Pythonic Review Checklist¶
Per-area signals for the review step of the pythonic-project-review skill.
No Python project must satisfy every item below. Treat these as questions that identify designs worth investigating, not as mechanical rules.
Cognitive load¶
Ask first:
- How much state must a reader hold in mind to understand this code?
- How many files must be crossed to understand one simple behavior?
- Is there unnecessary abstraction or indirection?
- Can a concept be deleted instead of added?
- Is the happy path visible at a glance?
- Is the API easy to use correctly?
Lowering cognitive load takes priority over every other concern in this document.
Functions¶
Check:
- does the function have multiple independent responsibilities
- too many parameters
- many boolean flags
- does it mix calculation with unrelated side effects
- does it depend on hidden global state
- deep nesting
- can guard clauses simplify the control flow
- are helpers split too finely
- are generators or context managers used where a straight-line function is clearer
- does the name accurately describe the behavior
Do not use line count as the primary reason to split a function.
Classes¶
For each important class ask:
Why is this a class?
Check whether it is only:
- a namespace
- a set of static methods
- a wrapper around a single callable
- stateless
- an interface forced in for dependency injection
Also check:
- is mutable state excessive
- is inheritance necessary
- would composition be more natural
- does the constructor perform I/O or complex business logic
- do properties have surprising side effects
- does it overload operators on a type that is not numeric or collection-like
Data modeling¶
Look for:
- dictionary-driven programming
dict[str, Any]- magic strings and magic numbers
- several related parameters always passed together
- tuples whose field meaning depends on position
- booleans that actually express an enum or state
- nullable fields that force defensive checks everywhere
Consider whether a dataclass, Enum, TypedDict, NamedTuple, Protocol, or
an explicit domain type fits better. Do not create meaningless classes for
simple data.
Types¶
Check:
Anycast# type: ignore- overly complex generics
- annotations inconsistent with runtime semantics
- public API types that are too wide
- parameters that should accept an abstract collection interface
- unstable return types
Types should express the design, not drive the design to become more complex.
Control flow¶
Look for:
- deeply nested
if - nested loops
- state variables
- duplicated conditions
- long
if/elifchains - exception-driven normal flow
Keep the normal path linear.
Exceptions¶
Check:
- bare
except except Exception- swallowed exceptions
- catch-log-reraise
- lost exception context
- error messages without context
- exceptions used for ordinary branches
- unnecessary custom exception hierarchies
Follow EAFP, but keep exception boundaries clear.
Mutability¶
Check:
- mutable defaults
- shared mutable state
- hidden mutation of arguments
- import-time mutation
- global caches
- singletons
- unclear object lifetimes
Prefer local, explicit state.
Resource management¶
Check files, sockets, locks, subprocesses, temporary files, and external
connections. Ask whether their lifetime can be expressed with with, a context
manager, contextlib, or ExitStack.
Standard library¶
Check for needless reimplementation of:
pathlibcollectionsitertoolsfunctoolscontextlibdataclassesenumtypingtempfilesubprocessshlexjsoncsvloggingurllib.parsedatetimezoneinfo
Using the standard library still requires the result to be simpler.
Modules¶
Check:
- does the module have multiple unrelated responsibilities
utils.pyandhelpers.pycatch-alls- cyclic imports
- import direction
- package boundaries
- private implementation leakage
__init__.pyside effects
Do not fragment the codebase into one-file-per-class structures.
Public API¶
Check:
- naming
- defaults
- keyword-only parameters
- positional argument ambiguity
- error semantics
- return types
- backward compatibility
- internal representation leakage
The API should be easy to use correctly and difficult to use incorrectly.
Testing¶
Check:
- tests coupled to implementation details
- excessive mocks
- fragile fixtures
- duplicated setup
- missing edge cases
- missing failure-path tests
- whether public behavior is genuinely verified
Code that is hard to test is often a design signal.
Dependencies¶
Ask:
- is the dependency really needed
- would the standard library be enough
- is it pulled in for a few lines of functionality
- is a runtime dependency misplaced as a dev dependency
- are compatibility workarounds already obsolete
Do not rewrite mature tools to chase zero dependencies.
Comments and docs¶
Check:
- comments that merely translate the code
- outdated comments
- obsolete
TODOs - inaccurate or trivial docstrings
- missing "why"
Let the code express "what" and comments explain "why".
Delete candidates¶
Look for:
- dead code
- unused helpers
- obsolete compatibility layers
- duplicated implementations
- unnecessary wrappers
- unused dependencies
- stale configuration
- abandoned feature flags
Deletion is often the most effective simplification.
Performance¶
Handle only clear problems:
- accidental O(n^2)
- repeated I/O
- repeated parsing
- unnecessary materialization
- avoidable large copies
- unnecessary work in a hot path
For anything not evident from the code itself:
benchmark before optimizing.
Do not perform speculative micro-optimizations.