A Code Review Checklist for Python Projects

Code review is where a team's conventions are actually applied, and where most of them are learned. It works best when machines handle everything mechanical and people spend their attention on what only people can judge. This checklist is organised in that order.
Step 0: automate the mechanical part
A review comment about whitespace or import order is time wasted on both sides, and it makes reviews feel petty. Settle those questions with tools before a human looks at the change:
- A formatter (Black or ruff's formatter) so layout is never discussed.
- A linter (ruff, flake8 or pylint) for PEP 8 checks and likely errors such as unused imports or undefined names.
- A type checker if the project uses annotations.
- The test suite, in continuous integration, on every pull request.
If a project does not have these yet, introducing them is itself a worthwhile change; our guide to adopting a style guide in an existing codebase describes how to do it without disrupting everyone. For a quick check of a snippet you are about to paste into a review comment, the checker on our home page runs a subset of pycodestyle's checks in the browser.
The reviewer's checklist
Go roughly in this order: there is no point polishing names in code that does the wrong thing.
1. Purpose and behaviour
- Do I understand what problem the change solves? Does the description say so?
- Does the code do that, including edge cases: empty input,
None, zero, very large values, duplicates, Unicode text, time zones? - For a bug fix, could I reproduce the bug, and does the fix address the cause rather than the symptom? Python's own developer guide asks reviewers to reproduce the issue, apply the change and look for missed corner cases.
2. Tests
- Are there tests for the new behaviour, and do they fail without the change?
- Do they test behaviour rather than implementation details, so they survive refactoring?
- Are the test names descriptive enough to act as documentation?
3. Design and structure
- Is the code in the right place? Does it duplicate something that already exists?
- Are functions small and focused, with one level of abstraction each?
- Is the public interface (names, arguments, return values) one we are happy to support? Internal helpers should be marked with a leading underscore.
- Is it simpler than it could be, or more general than it needs to be?
4. Names and readability
- Do names describe what things are in the problem domain, following PEP 8's formats?
- Is the control flow flat enough to follow? Would an early return or an extracted function help?
- Are comments explaining why, and are they still accurate? Do public functions have docstrings?
5. Errors and resources
- Are exceptions specific? Is there a bare
except:or anexcept Exception: passthat hides failures? - Are
tryblocks as small as possible, and is exception chaining (raise ... from) used when wrapping errors? - Are files, connections and locks released with
with? Are text files opened with an explicit encoding?
The programming recommendations of PEP 8 cover several of these points with examples.
6. Security and data
- Is external input validated? Are SQL queries parameterised rather than built with string formatting?
- Are secrets kept out of the code and the logs? Does any new logging write personal data?
- Are new dependencies necessary, maintained and pinned the way the project pins dependencies?
7. Performance, where it matters
- Is there an obvious problem, such as a database query or network call inside a loop, or quadratic work on data that can grow?
- Otherwise, prefer clarity. Optimisation without a measurement is a guess.
8. Compatibility and documentation
- Does the change break callers, configuration files or stored data? If so, is that intended and documented?
- Are the README, changelog or user documentation updated where needed?
Writing review comments
- Be specific and explain why. "This will raise
KeyErrorwhen the customer has no address;.get()with a default would cover it" helps; "this looks wrong" does not. - Separate blocking from optional. Many teams prefix minor suggestions with "nit:" or "optional:", so the author knows what must change before merging.
- Ask questions when unsure. "What happens if this list is empty?" invites an explanation and sometimes reveals that you missed something.
- Comment on the code, not the person, and say what is good. Reviews are also how people learn a codebase.
- Do not request changes that a tool should enforce. If you keep making the same style comment, propose a linter rule instead.
Approving is a statement too. Python's developer guide asks reviewers not to simply approve, but to say what they checked: that they reproduced the issue, ran the tests or tried the change.
The author's side
- Keep changes small. A focused pull request gets a careful review; a huge one gets a skim.
- Explain the intent in the description: what, why, how it was tested, what to look at first.
- Review your own diff first. Debug prints, commented-out code and unrelated reformatting are easy to spot before anyone else does.
- Separate refactoring from behaviour changes, ideally in different commits or pull requests.
- Answer every comment, with a change or a reason, and do not take suggestions personally.
Reviewing in a new team
Reading other people's pull requests is the fastest way to learn a codebase's unwritten conventions, which makes reviewing a good habit from your first week in a new job. If that job also came with a move, our checklist for your first months after relocating covers the rest of the transition. For the code itself, the guide to readable Python code develops the questions in section 4.
Sources
- Python Developer's Guide, "Lifecycle of a pull request": "How to review a pull request" and "Leaving a pull request review on GitHub".
- PEP 8 – Style Guide for Python Code.
- Python documentation,
sqlite3: how to use placeholders to bind values in SQL queries.
Readable code in practiceReadable code, beyond PEP 8
Code can pass every linter and still be hard to follow. Names, flat control flow, small functions and idioms that say what they mean, with a before-and-after.
Readable code in practiceAdopting a style guide in old code
One big reformat or a gradual rollout? How to bring an old codebase in line with PEP 8 while keeping git blame useful and open branches mergeable.
PEP 8, section by sectionPEP 8 programming recommendations
Why `is None` beats `== None`, why bare `except:` is risky and why a lambda should not get a name: the habits at the end of PEP 8, with tested examples.