---
description: Python Patterns and Idioms
alwaysApply: false
---

# Python Patterns and Idioms

Idiomatic Python leverages generators, decorators, context managers, and the data model. Write Pythonic code, not Java-in-Python.

## Data Model

Use `@dataclass(frozen=True, slots=True)` for immutable value objects. Implement `__repr__`, `__eq__`, `__hash__` automatically. Use `@total_ordering` for comparison.

## Generators

```python
# Lazy evaluation — process one item at a time
def read_large_file(path: Path) -> Iterator[str]:
    with open(path) as f:
        for line in f:
            yield line.strip()

# Generator expressions over list comprehensions when iterating once
total = sum(order.total for order in orders)

# itertools for complex iteration
for batch in batched(items, 100):  # 3.12+
    process_batch(batch)
```

## Decorators

```python
from functools import wraps
from typing import ParamSpec, TypeVar

P = ParamSpec("P")
R = TypeVar("R")

def retry(max_attempts: int = 3):
    def decorator(func: Callable[P, R]) -> Callable[P, R]:
        @wraps(func)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            for attempt in range(max_attempts):
                try: return func(*args, **kwargs)
                except Exception:
                    if attempt == max_attempts - 1: raise
        return wrapper
    return decorator
```

## Context Managers

```python
@contextmanager
def timed_operation(name: str) -> Iterator[None]:
    start = time.monotonic()
    try: yield
    finally: logger.info(f"{name} took {time.monotonic() - start:.3f}s")

# contextlib.suppress for intentionally ignored exceptions
with suppress(FileNotFoundError):
    os.remove(temp_file)
```

## Anti-Patterns

```python
# Never: Mutable default arguments
def append(item, target=[]):  # Shared across all calls!
    target.append(item)
# Never: Bare except — catches KeyboardInterrupt, SystemExit
try: risky()
except: pass
# Never: Star imports — pollutes namespace, hides origins
from os.path import *
```
