Readability counts
Choose explicit names, linear control flow, and small interfaces. Code is read far more often than it is written.
Make intent visible →Learn the standard, then check your own code against it. Get line-level guidance for readable, typed, testable, production-ready Python—without sending your code anywhere.
from collections.abc import Iterable
def active_names(
users: Iterable[User],
) -> list[str]:
"""Return sorted names of active users."""
return sorted(
user.name
for user in users
if user.is_active
)
Built-in utility
Get an immediate, line-by-line review based on this guide’s core principles. Every finding explains what matters and suggests a concrete next move.
FORMATIndentation · whitespace · line length · statements
INTENTNaming · docstrings · type contracts · complexity
SAFETYExceptions · secrets · shell use · timeouts
SCOPEFast heuristic review · not a parser or CI replacement
01Instant style reviewLine-level, private feedback
02PEP 8 groundedCanonical rules, with context
03Production mindedTypes, tests, security, CI
04Actionable guidanceEvery finding explains why
The north star
Great Python feels unsurprising. Readers spend their attention on the problem—not decoding clever syntax, inconsistent structure, or invisible side effects.
Choose explicit names, linear control flow, and small interfaces. Code is read far more often than it is written.
Make intent visible →Validate at the edges, type public contracts, and keep side effects easy to find. Make invalid states hard to represent.
Design clear contracts →Let formatters, linters, type checkers, and tests settle mechanical questions before review begins.
Build the safety net →Beautiful is better than ugly.
— The Zen of Python, PEP 20
Explicit is better than implicit.
Simple is better than complex.
The practical guide
Use these as strong defaults. Every rule has a purpose; when the rule obscures that purpose, use judgment and document the exception.
Format & name
Formatting should be boring, predictable, and automated. Names should carry enough meaning that comments explain why, not what a variable contains.
Never mix tabs and spaces. Use hanging indents for multiline expressions, and align closing delimiters with the construct that opened them.
result = build_report(
source,
include_meta=True,
)
PEP 8 specifies 79 characters for code and 72 for flowing text. Many formatter-led teams adopt 88. Pick one limit, configure it once, and let the formatter enforce it.
Use snake_case for functions and variables, PascalCase for classes, and UPPER_SNAKE_CASE for constants. Avoid vague containers like data, info, and utils.
invoice_totalvariable
PaymentClientclass
MAX_RETRIESconstant
def calc(d, f=False):
x = sum(i["v"] for i in d)
return x * 1.2 if f else x
def invoice_total(
line_items: Iterable[LineItem],
*,
include_tax: bool = False,
) -> Decimal:
subtotal = sum(item.price for item in line_items)
return subtotal * TAX_RATE if include_tax else subtotal
Pythonic patterns
Pythonic code uses the language’s strengths without turning every expression into a puzzle. Prefer a clear loop over a dense comprehension with nested conditions.
Return or raise at the boundary so the happy path stays flat and readable.
Prefer if items to if len(items) > 0; use is None when None has distinct meaning.
Use is for identity, == for value, and chained comparisons for ranges.
Reach for enumerate, zip, and dictionary views before manually managing indices.
def dispatch(order: Order, warehouse: Warehouse) -> Receipt:
"""Dispatch an order or raise a precise domain error."""
if not order.items:
raise EmptyOrderError(order.id)
if order.status is not OrderStatus.PAID:
raise OrderNotPaidError(order.id)
available_items = [
item for item in order.items if warehouse.has_stock(item.sku)
]
return warehouse.dispatch(available_items)
Types & boundaries
Type hints make interfaces searchable and mistakes cheaper. They are most valuable at public APIs, domain boundaries, and code that changes often.
Accept the broadest interface you need: Iterable, Sequence, Mapping, or a Protocol.
Return a concrete, predictable type. Avoid unions that force every caller to branch.
Use object for an unknown-but-type-safe value; reserve Any for truly dynamic boundaries.
from collections.abc import Iterable
from dataclasses import dataclass
from decimal import Decimal
from typing import Protocol
class Priced(Protocol):
@property
def price(self) -> Decimal: ...
@dataclass(frozen=True, slots=True)
class BasketSummary:
item_count: int
total: Decimal
def summarize(items: Iterable[Priced]) -> BasketSummary:
prices = [item.price for item in items]
return BasketSummary(len(prices), sum(prices, start=Decimal()))
Set a version floor. Declare requires-python, then use the modern syntax that floor supports. For current code, prefer built-in generics such as list[str] and unions such as str | None.
Functions & data
None.Keep policy at the center. Push frameworks, I/O, and vendor details to adapters at the edge.
Errors & logging
Exceptions are part of your API. Catch only what you can handle, preserve the cause, and make the operational trail useful without leaking sensitive data.
try:
charge(card)
except Exception:
print("Something went wrong")
return False
try:
receipt = gateway.charge(request)
except GatewayTimeout as exc:
logger.warning(
"Payment timed out",
extra={"order_id": order.id},
)
raise PaymentUnavailable(order.id) from exc
Name the failure in the language of the caller. Keep exception hierarchies shallow.
Create module loggers with logging.getLogger(__name__). Do not call basicConfig from a library.
Treat tokens, credentials, payment data, and personal information as toxic. Redact at the boundary.
Project structure
acme-payments/
├── pyproject.toml
├── README.md
├── src/
│ └── acme_payments/
│ ├── __init__.py
│ ├── domain/
│ ├── services/
│ └── adapters/
└── tests/
├── unit/
└── integration/
pyproject.tomlCentralize build metadata and supported tool configuration.
src layoutIt prevents accidental imports from the repository root and makes packaging mistakes visible.
Prefer cohesive modules over a catch-all utils.py. Mirror concepts, not framework jargon.
Import order
Testing
FastUnit tests run constantly.
FocusedOne behavior, clear failure.
FaithfulIntegration tests cover real boundaries.
IndependentNo order or shared-state surprises.
import pytest
@pytest.mark.parametrize(
("subtotal", "discount", "expected"),
[
(Decimal("100"), Decimal("0.10"), Decimal("90.00")),
(Decimal("0"), Decimal("0.10"), Decimal("0.00")),
],
)
def test_discounted_total(subtotal, discount, expected):
assert discounted_total(subtotal, discount) == expected
Coverage is a map, not a target. Use it to find untested risk; a high percentage cannot prove useful assertions.
Reliability
eval, unsafe deserialization, and shell strings.asyncio for structured I/O concurrency.Make failure finite. Network calls need explicit timeouts, retries need backoff and a cap, queues need bounds, and shutdown paths need tests.
The modern toolchain
Use one command locally and the same command in CI. The exact tools can change; the feedback loop should remain fast and dependable.
A pragmatic baseline
Put supported configuration in pyproject.toml. Start with a focused rule set; add stricter rules because they catch problems your team actually has—not because a tool offers them.
[project]
name = "your-project"
version = "0.1.0"
requires-python = ">=3.12"
[tool.ruff]
target-version = "py312"
line-length = 88
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"F", # Pyflakes
"I", # import sorting
"UP", # modern Python syntax
"B", # bugbear
"SIM", # simplification
]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
docstring-code-format = true
[tool.pytest.ini_options]
addopts = ["--strict-markers", "--strict-config"]
testpaths = ["tests"]
[tool.mypy]
python_version = "3.12"
warn_unused_configs = true
check_untyped_defs = true
no_implicit_optional = true
NoteThis is a starting point, not universal law. Match requires-python, Ruff’s target, and your type checker’s Python version to the oldest runtime you actually support.
Before you merge
A compact review for the risks automation cannot fully understand. Your progress is saved on this device.
The Python bookshelf
Category-defining bestsellers and enduring practitioner favorites, selected for a useful path from first program to maintainable production systems.
Start hereCrash Course→Automate
Write better codeEffective→Fluent
Build for changeRobust→Architecture
A fast, project-led introduction that moves from syntax and testing to games, data visualization, and web applications.
Turns fundamentals into immediate wins: files, spreadsheets, PDFs, web tasks, databases, messages, and desktop automation.
Concise, specific guidance on Pythonic thinking, functions, comprehensions, classes, concurrency, testing, and performance.
A deep tour of Python’s data model, functions, typing, protocols, generators, concurrency, and metaprogramming.
Types, interface design, extensibility, static analysis, and testing strategies for codebases that need to survive change.
A hands-on guide to domain models, repositories, units of work, dependency inversion, message buses, and event-driven systems.
Editorial noteSelections are independent and use current English-language editions reviewed July 2026. “World bestseller” reflects the publisher’s description of Python Crash Course; the remaining labels are editorial recommendations, not live sales rankings. No affiliate links or paid placements.
Primary sources
This guide synthesizes standards and practice. When precision matters, follow the living source.
Python is a trademark of the Python Software Foundation. This independent guide is not affiliated with or endorsed by the PSF. Last editorial review: July 18, 2026.
Copied to clipboard