Python Atlas
Software engineering

PEP 8 and clean Python

Use consistent style, clear names, and small units without turning code into code golf.

PEP 8 is a shared visual vocabulary for Python. It reduces avoidable discussion and makes structure easier to scan. It does not guarantee good design: a formatter can normalize whitespace, but it cannot choose a useful name or separate unrelated responsibilities.

Automate mechanical style

Use a formatter and linter in local development and CI. Let tools settle line wrapping, spacing, import order, and common mistakes. Spend review time on behavior and design.

# pyproject.toml
[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]

[tool.ruff.format]
quote-style = "double"

Configure one source of truth. Conflicting line lengths across an editor, formatter, and linter create noise rather than quality.

Name by role and unit

Names should reveal meaning at the point of use.

def shipping_cost(weight_kg: float, distance_km: int) -> float:
    base_fee = 4.50
    distance_fee = distance_km * 0.015
    weight_fee = max(weight_kg - 1.0, 0.0) * 0.80
    return base_fee + distance_fee + weight_fee

The units in weight_kg and distance_km prevent an entire class of misunderstandings. Avoid vague names such as data, result, process, and manager when a domain term exists. Short names are reasonable for narrow conventions such as x, y, or a tiny comprehension.

Prefer readable control flow

Guard clauses keep the successful path visible:

from pathlib import Path


def read_report(path: Path) -> str:
    if not path.exists():
        raise FileNotFoundError(f"Report does not exist: {path}")
    if path.is_dir():
        raise IsADirectoryError(f"Expected a file: {path}")

    return path.read_text(encoding="utf-8")

Do not compress branches merely to reduce line count:

# Harder to debug and extend.
label = "large" if size > 100 else "medium" if size > 20 else "small"

Three explicit branches are longer but make breakpoints, additions, and review easier.

Keep functions cohesive

A function should operate at one level of abstraction. Parsing, domain decisions, persistence, and presentation are different concerns even if they fit in 20 lines.

def active_email_addresses(rows: list[dict[str, object]]) -> list[str]:
    addresses: list[str] = []
    for row in rows:
        if row.get("active") is True:
            email = row.get("email")
            if isinstance(email, str) and email:
                addresses.append(email.casefold())
    return addresses

At an external boundary, replace unstructured mappings with validated domain data as early as possible. Type hints help tools and readers, but they do not validate runtime input.

Comments explain why

Good comments preserve information that code cannot express:

# The upstream service treats repeated requests within 30 seconds as duplicates.
RETRY_DELAY_SECONDS = 31

Comments that narrate syntax become stale:

# Increment attempts by one.
attempts += 1

Use docstrings for public contracts, surprising side effects, raised exceptions, and important units. Avoid repeating the signature in prose.

Practical PEP 8 rules

  • use four spaces for indentation, never tabs mixed with spaces;
  • use snake_case for functions and variables, PascalCase for classes, and UPPER_CASE for constants;
  • place imports at the top in standard-library, third-party, and local groups;
  • compare with None using is None, not == None;
  • use if items instead of if len(items) > 0;
  • keep public and internal names deliberate; a leading underscore signals internal use;
  • catch the narrowest exception you can handle meaningfully.

Pitfalls

  • treating every linter warning as an absolute law without understanding it;
  • hiding side effects in properties or innocent-looking helper functions;
  • using except Exception: pass, which discards evidence;
  • writing one-line functions that combine validation, mutation, and I/O;
  • extracting every repeated two-line fragment before its common concept is understood;
  • renaming domain terms to generic technical names.

Review checklist

  • Names communicate role, units, and domain meaning.
  • The main path is easy to follow.
  • Functions have one coherent purpose.
  • Exceptions preserve actionable context.
  • Comments explain constraints or intent, not syntax.
  • Formatter and linter settings agree.
  • Dense expressions have been expanded when clarity improves.

Practice

Take a function with nested conditionals. Write its behavior as a small decision table, then refactor it using descriptive predicates and guard clauses. Confirm behavior with tests before and after. Compare cognitive complexity, not only line count.

LEARNING RECORD

Finish this lesson

Mark it complete when you can explain the main decision without looking.

KNOWLEDGE CHECK

PEP 8 and clean Python

1How should a team use formatting and linting tools to support PEP 8 effectively?
2A 20-line function parses input, applies business rules, writes to a database, and formats output. What is the strongest reason to split it?