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_feeThe 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 addressesAt 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 = 31Comments that narrate syntax become stale:
# Increment attempts by one.
attempts += 1Use 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_casefor functions and variables,PascalCasefor classes, andUPPER_CASEfor constants; - place imports at the top in standard-library, third-party, and local groups;
- compare with
Noneusingis None, not== None; - use
if itemsinstead ofif 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