Skip to content

Human Readability

Write Python for people reading, reviewing, debugging, and changing it. Favor explicit meaning, a predictable reading order, and a small amount of context to hold in memory. Consider the plausible wrong interpretation a reader might make, then make the important distinction visible in the code or its documentation.

Names, structure, comments, and docstrings serve different purposes. Names identify concepts and operations; structure exposes the flow of work; docstrings describe contracts; comments preserve non-obvious reasons and constraints. Repeating the same information across these surfaces adds reading effort without adding understanding.

Make the Code Easy to Follow

Prefer code whose ordinary behavior is understandable from a local reading:

  • Keep related work together and make state changes visible near the operation that causes them.
  • Break dense expressions into meaningful intermediate values when this reveals intent. Fewer lines are not necessarily easier to read.
  • Use straightforward control flow and guard clauses where they reduce nesting without hiding important cleanup or ordering.
  • Extract a function when it names a coherent responsibility, not merely to move a few lines elsewhere. Excessive indirection forces readers to jump between files to reconstruct one operation.
  • Prefer established idioms over clever shortcuts. Avoid using truthiness when a reader needs to distinguish None, zero, an empty collection, and False.
  • Keep representation and lifecycle transitions explicit: parsing, validation, persistence, and publication are different operations even when one function coordinates them.

Improve unclear names or structure before adding prose to explain them. Use documentation for information that cannot be expressed cleanly in the code, not as compensation for avoidable ambiguity.

Naming Conventions

Prefer names that state what a value represents, what a function guarantees, and which distinctions affect correct use. A reader should not need to inspect the implementation or infer meaning from the current type to understand ordinary code.

Use snake_case for functions, variables, and parameters; PascalCase for classes; and UPPER_SNAKE_CASE for module-level constants. Use short lowercase names for modules and packages; underscores can improve module readability, while PEP 8 discourages them in package names. Follow PEP 8 naming conventions unless the surrounding public API has an established compatible convention.

Package Layout

A package tree is a map for readers. Its directories should identify coherent responsibilities, and its module names should help someone predict where a behavior is implemented. Prefer grouping code that changes together and shares a domain vocabulary over grouping unrelated code merely because it uses the same framework or language construct.

For an application with distinct ordering and billing responsibilities, a possible layout is:

src/
    shop/
        __init__.py
        orders/
            __init__.py
            models.py
            validation.py
            repository.py
        billing/
            __init__.py
            invoices.py
            payment_gateway.py
tests/
    orders/
    billing/

This layout gives shop.orders.validation a clear scope: order validation, not every kind of validation in the application. The example is not a required template. A small project may be clearer with a few modules in one package; introduce subpackages when they represent a real boundary, not to make the tree appear architectural.

  • Keep an end-to-end responsibility easy to trace. Global directories such as models/, services/, and helpers/ can scatter one feature across the tree; use them when a genuine shared layer justifies that separation.
  • Keep dependencies understandable. Domain rules should not need to import an HTTP endpoint merely to reuse its validation. Repeated circular imports often indicate responsibilities that overlap or a shared concept that lacks a clear owner.
  • Split a module when it contains independently understandable responsibilities or has dependencies unrelated to much of its code. Do not impose one file per class or split solely because a file reaches an arbitrary line count.
  • Avoid deep nesting that adds no meaning. Each package level should narrow the responsibility enough to justify another step in navigation and another component in an import path.
  • Give shared code a specific owner and purpose. Move behavior into a shared package when multiple callers genuinely need the same contract, not because its first home is inconvenient.
  • Keep tests easy to locate by reflecting meaningful source boundaries where the project's test conventions allow it.

A src/ layout separates importable application code from repository-level files and helps expose accidental imports from the working directory. It is a packaging choice, not a readability requirement or a reason to reorganize an existing project unnecessarily. See the Python Packaging User Guide's layout comparison.

Module Names and Import Readability

Choose module names for the responsibility they contain, not for the fact that they contain Python code. Read the full import path before deciding whether a name is clear.

Vague name More informative alternative Distinction made visible
utils.py date_ranges.py The shared operations concern date intervals.
helpers.py invoice_formatting.py The code formats invoices rather than performing arbitrary support work.
common.py identifiers.py The shared concept is identifier handling.
manager.py connection_pool.py The responsibility is managing pooled connections.
processing.py order_import.py The operation imports orders rather than doing unspecified processing.

These are examples of semantic distinctions, not mandatory replacements. A framework's established models.py or a narrowly scoped orders.validation can be clearer than an unfamiliar naming scheme. Avoid redundant names such as orders.order_validation when orders.validation already supplies the needed context; retain a qualifier when it distinguishes multiple responsibilities within that package.

Prefer importable identifier names: lowercase words, no hyphens or spaces, and a consistent vocabulary across sibling modules. Avoid local modules named typing.py, logging.py, json.py, or after an installed dependency when they could shadow the intended import. Distinguish a distribution's installation name from its import package name; they need not match. See distribution packages and import packages.

Imports should expose origin without making readers decode unnecessary aliases. Use from shop.orders.validation import validate_order when the symbol identifies its role clearly; use a module-qualified form such as order_validation.validate(payload) when an otherwise generic function name needs its namespace. Avoid wildcard imports and aliases that obscure which subsystem owns a symbol. See PEP 8's import guidance.

Package Boundaries and Entry Points

Keep __init__.py small and intentional. For ordinary packages it identifies the package and may expose a curated public API; namespace packages are an intentional exception that can omit it. Do not make importing a package unexpectedly start services, open network connections, or load configuration. See the Python package documentation.

Re-export a symbol when doing so gives callers a stable, understandable public boundary. Do not re-export every implementation detail or build chains of forwarding modules that make definitions difficult to find. Where used, __all__ declares intended exports but does not enforce privacy; a leading underscore signals an internal module or symbol by convention, not access control.

Keep executable entry points thin: __main__.py or a CLI module should arrange startup and delegate to the owning application code. Readers should be able to distinguish orchestration from the domain behavior they need to inspect or reuse. A module docstring can explain a non-obvious boundary or dependency rule, but "Order utilities" adds little to a module already named for orders.

Name the Meaning

Choose a concrete domain noun for data and an action verb for behavior. Include the qualifier that separates nearby concepts when it changes a caller's decision.

# Ambiguous: the reader must discover what each name represents.
data = load(path)
result = transform(data)

# Clear: the type and role are evident before following the implementation.
configuration_text = configuration_path.read_text()
normalized_configuration = normalize_configuration(configuration_text)

Do not use broad placeholders such as data, info, value, result, item, thing, manager, or handler when a domain name is available. They are acceptable only in a small, conventional scope where the surrounding construct supplies the meaning, such as key and value in a short mapping loop.

Prefer one term for one concept. If the code calls the same identifier an account, user, and customer in different places, readers cannot tell whether those names are synonyms or distinct roles. Define the distinction in the domain model and preserve it in names.

State, Time, Units, and Boundaries

Expose details that alter interpretation or arithmetic. A bare number or timestamp is rarely self-explanatory.

Ambiguous Prefer Reason
timeout timeout_seconds States the unit.
size payload_size_bytes States what is measured and its unit.
created created_at or creation_date Separates an instant from a calendar date.
start, end start_inclusive, end_exclusive States range membership at the boundary.
count retry_count or maximum_retries Separates an observed quantity from a limit.
id order_id States which entity owns the identifier.

Use a name that states the time semantics when they matter: expires_at for a timestamp, expires_on for a date, elapsed_seconds for a duration, and retry_deadline for a cutoff. Do not make a reader guess whether timestamp is UTC, local time, creation time, or last-update time; encode the known distinction, such as created_at_utc or last_synced_at.

For ranges and slices, name both endpoints according to the contract. Python's normal convention is a start-inclusive, end-exclusive interval, so start_index and stop_index are sufficient when code clearly follows that convention. Otherwise use explicit suffixes and document the exceptional rule.

Collections and Relationships

Use plural names for groups and singular names for one member. Name a mapping for its lookup relationship, not merely the values it contains.

orders_by_id: dict[str, Order]
active_user_ids: set[str]
failed_job: Job

for order_id, order in orders_by_id.items():
    process_order(order)

Do not claim a concrete container in a name unless callers depend on it. user_ids works for any collection of identifiers; use user_id_list only when list ordering or list operations are part of the contract. Similarly, orders_by_id is clearer than orders when key-based lookup is its purpose.

Use names that distinguish an entity, its identifier, and a serialized representation: user, user_id, and user_payload are separate things. A variable called user should not sometimes contain a User object and sometimes a dictionary received from an API.

Functions and Methods

Start functions with a verb that describes their primary effect and return contract. Pick verbs consistently; readers learn the API's vocabulary from repeated use.

Verb Expected meaning
parse_* Convert structured input into an in-memory representation; invalid input usually raises an error.
format_* / serialize_* Convert a value into a presentation or transport representation.
load_* Obtain a value from storage, configuration, or another external source.
create_* Construct a new domain object or persistent resource.
update_* / delete_* Mutate or remove an existing resource.
find_* Look up an optional result; return None when absence is normal.
get_* Return an available value; document or encode whether absence has a default or raises.
require_* Return a required value and raise a domain-appropriate error when it is absent.
ensure_* Make a condition true, usually idempotently.
validate_* Check a contract; state whether invalid input raises or returns diagnostics.

Avoid using get_* for a method that creates, writes, refreshes, or otherwise has a surprising side effect. Prefer load_or_create_*, refresh_*, or another name that reveals the effect. Likewise, a predicate should answer a question rather than perform work: is_valid() is clearer than validate() when it returns only True or False.

Boolean Names

Phrase booleans as claims that are true or false. Prefix a condition with is_, has_, can_, should_, or a similarly precise auxiliary when that improves reading.

if user.is_active:
    send_notification(user)

if request.has_valid_signature:
    accept_request(request)

Prefer positive names when both choices are equally natural: is_enabled makes the enabled path read directly. Use a negative term when absence or prohibition is the actual domain state, such as is_archived, is_read_only, or has_no_remaining_attempts. Avoid double negatives like not is_not_ready and verbs disguised as flags such as send_email for a value that only permits sending.

Scope and Conventional Names

Let scope determine how much context a name needs. A public parameter, attribute, log field, or value that crosses a function boundary should be understandable in isolation. A short local expression may use a conventional compact name when its meaning is immediately visible.

  • Use error for a caught exception, path for a filesystem path, and index for a sequence position when no further qualifier is needed.
  • Avoid one-letter names outside established mathematical, coordinate, or short comprehension scopes. x, i, and tmp force readers to retain hidden context.
  • Avoid shadowing built-ins and important imports: use user_id rather than id, input_text rather than input, and result_type rather than type.
  • Use lowercase initialisms in snake_case: http_client, user_id, and parse_url. In class names, use the project's existing acronym style; PEP 8's general form is HttpClient and UserId.

Type annotations refine a name; they do not repair an unclear one. items: list[dict[str, object]] still hides what the entries represent, while pending_order_payloads tells the reader why the collection exists.

Docstrings Must Add Information

Every docstring should tell the reader something useful that is not already obvious from the name and signature. A docstring saying "Creates a record" on create_record adds no information. Omit such filler where documentation is optional; where a public API or project convention requires a docstring, describe a meaningful part of its contract instead.

def create_record(payload: RecordPayload) -> Record:
    """Creates a record."""
    ...

For an implementation with the following persistence behavior, the useful documentation would be:

def create_record(payload: RecordPayload) -> Record:
    """Persist a record and return it with its storage-assigned identifier.

    Raises DuplicateRecordError if the payload's external ID already exists.
    The write commits before this function returns.
    """
    ...

The second docstring answers questions that create_record alone cannot: whether creation persists anything, who assigns the identifier, how duplicates behave, and when the write becomes durable. Document only guarantees the implementation actually provides; do not invent semantics to fill a template.

Useful docstring content includes:

  • Input constraints, units, supported formats, and the meaning of optional or sentinel values.
  • Return semantics, ordering, ownership, mutability, and whether absence produces None or an error.
  • Side effects, persistence and transaction boundaries, idempotency, and partial-failure behavior.
  • Expected exceptions and conditions that callers can meaningfully handle.
  • Resource lifetime, concurrency restrictions, cancellation behavior, or blocking work that affects callers.
  • A concise example when it resolves a genuine ambiguity about correct use.

Do not repeat type annotations in prose, mechanically list parameters whose names already explain them, or narrate implementation steps that callers do not need. Prefer the shortest accurate explanation of the public contract over boilerplate sections. See PEP 257 for docstring conventions.

Establish Context Before Rules

Write for a technical reader who understands standard programming concepts but does not yet know this implementation's contract. Introduce implementation-specific concepts before describing their behavior: establish that events carry priorities before explaining which priorities dispatch first. Do not explain familiar concepts such as queues, tasks, or exceptions unless this implementation gives them unusual semantics.

Use the summary, signature, and surrounding documentation as context rather than repeating what they already establish. Order explanations by dependency: purpose, relevant concepts, relationships, behavior, then exceptions and caveats. Name the operation or state precisely; unexplained shorthand such as "readiness," "acknowledgement," or "shutdown complete" can hide several plausible interpretations.

Be Concise Without Losing Meaning

Optimize for the shortest explanation that gives readers an accurate mental model, not the fewest words. Include details that affect correct use, maintenance, or interpretation; remove narration of obvious code and general programming tutorials.

  • Explain important distinctions explicitly: scheduled versus started, dispatched versus completed, and processing stopped versus callbacks finished.
  • Connect guarantees to their boundaries. State what an operation waits for, changes, orders, or leaves untouched.
  • Introduce a mechanism only when it helps explain the contract. Do not force readers to reconstruct behavior from implementation terminology.
  • Remove repetition before removing context. A caveat is not concise if understanding it requires opening several other helpers.
  • Document only behavior supported by the implementation. Narrow a claim rather than adding qualifications to an inaccurate generalization.

Match Documentation to Scope

  • Class docstrings establish the shared mental model and relationships among public operations.
  • Method docstrings explain the local contract and surprising behavior without repeating the entire class overview. Keep essential distinctions understandable when a method's documentation is read independently.
  • Property docstrings describe what the value means, especially when it differs from related state. Disclose side effects on access or assignment.
  • Internal helper docstrings explain non-obvious responsibilities, invariants, and failure boundaries. Omit filler on trivial helpers.

Use Google-Style Structure Purposefully

Use Google-style docstrings when a function has many arguments or any argument needs special explanation. An Args: section lets readers scan parameter-specific constraints, units, sentinel values, and interactions without extracting them from a dense paragraph. Match parameter names exactly and keep entries in signature order; explain semantics rather than repeat annotated types or paraphrase names.

def fetch_records(
    *, timeout_seconds: float, maximum_records: int | None = None
) -> list[Record]:
    """Fetch records in oldest-first order.

    Args:
        timeout_seconds: Total time budget, including retries; must be positive.
        maximum_records: Maximum number to fetch; None means no limit,
            and zero returns an empty list without contacting storage.
    """
    ...

Place parameter semantics in Args:, return guarantees in Returns:, context values supplied by a context manager in Yields:, and actionable failure conditions in Raises:. Use Attributes: for public state. Do not list exceptions raised only by later operations as though registration itself raises them.

Keep relationships and behavioral context in concise prose. Add sections only when they convey useful information; do not repeat annotated types or fill sections mechanically. Keep simple docstrings short when structured sections would add no information.

Comments Must Explain What Is Not Obvious

Use comments for a non-obvious reason, invariant, external constraint, or tradeoff that a maintainer needs to preserve. The test is whether removing the comment would lose useful knowledge that cannot be recovered easily from the nearby code.

# Increment the attempt count.
attempt_count += 1

That comment merely restates the operation. A useful comment explains a reason the expression cannot convey:

# Count before sending so a timeout still consumes an attempt.
attempt_count += 1

Avoid comments that label obvious operations, paraphrase a function name, or divide short code into ceremonial steps such as "Initialize variables" and "Return result". Explain what a subtle algorithm accomplishes when needed to orient the reader, but do not translate each statement into English.

Keep comments close to the code they qualify and update them when behavior changes. A stale explanation can be more misleading than no comment. Keep caller-facing guarantees in docstrings and implementation-specific rationale in comments; neither should substitute for executable validation of an important invariant.

Review Questions

Read the code as someone who did not write it, and ask:

  • Can a reader follow the main path without reconstructing distant state or opening many tiny helpers?
  • Can a reader predict which package and module own a behavior, then trace it through clear imports?
  • Does each package boundary express a responsibility rather than merely add a directory level?
  • Could a reader tell what this value represents without opening its producer?
  • Does the name distinguish this concept from nearby concepts such as an ID, payload, count, limit, date, timestamp, or duration?
  • Does a function name reveal its meaningful side effects and ordinary absence or failure behavior?
  • Would the code still be understandable if a type annotation were unavailable or stale?
  • Is the same domain concept named consistently throughout its public and cross-module use?
  • Does each docstring add a contract detail beyond the name and signature?
  • When reading each docstring in order, has every implementation-specific concept been introduced before its rules?
  • Can a technical reader predict the important behavior without studying the body?
  • Can any sentence be removed without losing necessary context or a meaningful distinction?
  • Does each comment preserve useful reasoning or context rather than narrate visible code?
  • Which plausible misconception remains, and can it be resolved with clearer code instead of more prose?

When a precise name becomes long, prefer the precise name. Refactor a crowded expression into named intermediate steps before shortening a name until its meaning becomes implicit.

References: PEP 8 naming conventions, PEP 20, and the Python glossary.