Python Patterns
Core Idioms
Prefer comprehensions over loops for transformations
# Good
evens = [x for x in range(10) if x % 2 == 0]
squared = {k: v**2 for k, v in data.items()}
# Bad
evens = []
for x in range(10):
if x % 2 == 0:
evens.append(x)
Use generators for large sequences
def read_chunks(file_path: Path, size: int = 8192) -> Generator[bytes, None, None]:
with open(file_path, "rb") as f:
while chunk := f.read(size):
yield chunk
Context managers for resource management
from contextlib import contextmanager
@contextmanager
def managed_connection(url: str):
conn = connect(url)
try:
yield conn
finally:
conn.close()
Dataclasses for structured data
from dataclasses import dataclass, field
from datetime import datetime
@dataclass(frozen=True)
class User:
id: int
name: str
email: str
created_at: datetime = field(default_factory=datetime.utcnow)
Named tuples for lightweight value objects
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
def distance(self, other: "Point") -> float:
return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5
Error Handling
Specific exceptions, not bare except
# Good
try:
result = parse_config(path)
except FileNotFoundError:
logger.warning("Config not found, using defaults")
result = DEFAULT_CONFIG
except json.JSONDecodeError as e:
raise ConfigError(f"Invalid JSON in config: {e}") from e
# Bad
try:
result = parse_config(path)
except: # catches SystemExit, KeyboardInterrupt, etc.
result = {}
Custom exception hierarchy
class AppError(Exception):
"""Base exception for this application."""
class ValidationError(AppError):
def __init__(self, field: str, message: str) -> None:
self.field = field
super().__init__(f"{field}: {message}")
class NotFoundError(AppError):
pass
Type Annotations
from typing import TypeVar, Callable, Awaitable
from collections.abc import Sequence
T = TypeVar("T")
def first(items: Sequence[T], predicate: Callable[[T], bool]) -> T | None:
return next((item for item in items if predicate(item)), None)
# Use | None (3.10+) not Optional[T]
def find_user(user_id: int) -> User | None: ...
# Use type aliases for complex types
JsonDict = dict[str, "JsonValue"]
JsonValue = str | int | float | bool | None | list["JsonValue"] | JsonDict
Common Anti-Patterns
- Mutable default arguments:
def f(items=[]) → use items: list | None = None
- Shadowing builtins:
list = [...], id = ...
- String concatenation in loops: use
"".join(parts) or f-strings
is for value comparison: x is 1 → use ==
- Not using
__slots__ on hot dataclasses in tight loops