python-patterns

Pythonic idioms, PEP 8 standards, type hints, and best practices for building robust, efficient, and maintainable Python applications.

8 stars

Best use case

python-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Pythonic idioms, PEP 8 standards, type hints, and best practices for building robust, efficient, and maintainable Python applications.

Teams using python-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/python-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/marvinrichter/clarc/main/skills/python-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/python-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How python-patterns Compares

Feature / Agentpython-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Pythonic idioms, PEP 8 standards, type hints, and best practices for building robust, efficient, and maintainable Python applications.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Python Development Patterns

Idiomatic Python patterns and best practices for building robust, efficient, and maintainable applications.

## When to Activate

- Designing Python package structure and module boundaries
- Choosing between dataclasses, Pydantic models, and TypedDicts for a data structure
- Applying idiomatic Python (list comprehensions, generators, context managers, decorators)
- Setting up type hints and mypy for a new module or existing codebase
- Structuring a Python service with dependency injection or hexagonal architecture
- Deciding when to use ABCs vs protocols for interface design

## Core Principles

### 1. Readability Counts

Python prioritizes readability. Code should be obvious and easy to understand.

```python
# Good: Clear and readable
def get_active_users(users: list[User]) -> list[User]:
    """Return only active users from the provided list."""
    return [user for user in users if user.is_active]


# Bad: Clever but confusing
def get_active_users(u):
    return [x for x in u if x.a]
```

### 2. Explicit is Better Than Implicit

Avoid magic; be clear about what your code does.

```python
# Good: Explicit configuration
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# Bad: Hidden side effects
import some_module
some_module.setup()  # What does this do?
```

### 3. EAFP - Easier to Ask Forgiveness Than Permission

Python prefers exception handling over checking conditions.

```python
# Good: EAFP style
def get_value(dictionary: dict, key: str) -> Any:
    try:
        return dictionary[key]
    except KeyError:
        return default_value

# Bad: LBYL (Look Before You Leap) style
def get_value(dictionary: dict, key: str) -> Any:
    if key in dictionary:
        return dictionary[key]
    else:
        return default_value
```

> **When to use which:** Use EAFP for external resources (files, APIs, DB) where failures are expected and informative. Use LBYL for simple attribute checks on known objects where a conditional read is cheaper than exception handling.

## Type Hints

### Type Annotations (Python 3.10+)

Use built-in generic types directly (`list[str]`, `dict[str, Any]`, `X | None`) — no `typing` imports for basic annotations:

```python
from typing import Any

def process_user(user_id: str, data: dict[str, Any], active: bool = True) -> User | None:
    return User(user_id, data) if active else None

def process_items(items: list[str]) -> dict[str, int]:
    return {item: len(item) for item in items}
```

### Type Aliases and Generics (Python 3.12+)

```python
from typing import Any

# Type alias (PEP 695 — Python 3.12+)
type JSON = dict[str, Any] | list[Any] | str | int | float | bool | None

def parse_json(data: str) -> JSON:
    return json.loads(data)

# Generic functions with new syntax (Python 3.12+)
def first[T](items: list[T]) -> T | None:
    """Return the first item or None if list is empty."""
    return items[0] if items else None

# Generic classes (Python 3.12+)
class Stack[T]:
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()
```

### Protocol-Based Duck Typing

```python
from typing import Protocol

class Renderable(Protocol):
    def render(self) -> str:
        """Render the object to a string."""

def render_all(items: list[Renderable]) -> str:
    """Render all items that implement the Renderable protocol."""
    return "\n".join(item.render() for item in items)
```

## Error Handling Patterns

### Specific Exception Handling

```python
# Good: Catch specific exceptions
def load_config(path: str) -> Config:
    try:
        with open(path) as f:
            return Config.from_json(f.read())
    except FileNotFoundError as e:
        raise ConfigError(f"Config file not found: {path}") from e
    except json.JSONDecodeError as e:
        raise ConfigError(f"Invalid JSON in config: {path}") from e

# Bad: Bare except
def load_config(path: str) -> Config:
    try:
        with open(path) as f:
            return Config.from_json(f.read())
    except:
        return None  # Silent failure!
```

### Custom Exception Hierarchy

```python
class AppError(Exception):
    """Base exception for all application errors."""
    pass

class ValidationError(AppError):
    """Raised when input validation fails."""
    pass

class NotFoundError(AppError):
    """Raised when a requested resource is not found."""
    pass

# Usage
def get_user(user_id: str) -> User:
    user = db.find_user(user_id)
    if not user:
        raise NotFoundError(f"User not found: {user_id}")
    return user
```

## Context Managers

Always use `with` for resource management (files, DB connections, locks). Prefer `@contextmanager` for simple cases; use `__enter__`/`__exit__` classes for stateful resources.

### Custom Context Managers

```python
from contextlib import contextmanager

@contextmanager
def timer(name: str):
    """Context manager to time a block of code."""
    start = time.perf_counter()
    yield
    elapsed = time.perf_counter() - start
    print(f"{name} took {elapsed:.4f} seconds")

# Usage
with timer("data processing"):
    process_large_dataset()
```

### Context Manager Classes

```python
class DatabaseTransaction:
    def __init__(self, connection):
        self.connection = connection

    def __enter__(self):
        self.connection.begin_transaction()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.connection.commit()
        else:
            self.connection.rollback()
        return False  # Don't suppress exceptions

# Usage
with DatabaseTransaction(conn):
    user = conn.create_user(user_data)
    conn.create_profile(user.id, profile_data)
```

## Comprehensions and Generators

```python
# List comprehension — prefer over manual loops
names = [user.name for user in users if user.is_active]

# Generator expression — lazy, no intermediate list (O(1) memory)
total = sum(x * x for x in range(1_000_000))

# Generator function — stream large data without loading all into memory
def read_large_file(path: str) -> Iterator[str]:
    with open(path) as f:
        for line in f:
            yield line.strip()
```

Keep comprehensions simple — if a comprehension needs more than one `if` or a nested loop, use a regular function instead.

## Data Classes

```python
from dataclasses import dataclass, field
from datetime import datetime

@dataclass
class User:
    """Automatic __init__, __repr__, __eq__. Use __post_init__ for validation."""
    id: str
    name: str
    email: str
    created_at: datetime = field(default_factory=datetime.now)
    is_active: bool = True

    def __post_init__(self):
        if "@" not in self.email:
            raise ValueError(f"Invalid email: {self.email}")
```

> For advanced patterns — `NamedTuple` with methods, function decorators, parameterized decorators, class-based decorators, concurrency (threading, multiprocessing, async/await), hexagonal architecture with FastAPI, and memory optimization — see `python-patterns-advanced`.

## Anti-Patterns

### Using a Mutable Default Argument

**Wrong:**
```python
def append_item(item, collection=[]):  # list is created once at definition time
    collection.append(item)
    return collection

append_item("a")  # ["a"]
append_item("b")  # ["a", "b"]  — unexpected: shares the same list
```

**Correct:**
```python
def append_item(item, collection=None):
    if collection is None:
        collection = []
    collection.append(item)
    return collection
```

**Why:** Default argument values are evaluated once when the function is defined; mutable defaults accumulate state across calls.

---

### Catching `Exception` (or bare `except`) and Swallowing It

**Wrong:**
```python
def load_user(user_id: str) -> User | None:
    try:
        return db.find(user_id)
    except Exception:
        return None  # hides programming errors, network failures, everything
```

**Correct:**
```python
def load_user(user_id: str) -> User | None:
    try:
        return db.find(user_id)
    except UserNotFoundError:
        return None
    except DatabaseError as e:
        raise ServiceUnavailableError("Database unreachable") from e
```

**Why:** Catching `Exception` silently swallows bugs and infrastructure failures, making them impossible to observe or recover from correctly.

---

### Building Strings with `+` in a Loop

**Wrong:**
```python
def build_csv(rows: list[dict]) -> str:
    result = ""
    for row in rows:
        result += ",".join(str(v) for v in row.values()) + "\n"
    return result
```

**Correct:**
```python
def build_csv(rows: list[dict]) -> str:
    lines = [",".join(str(v) for v in row.values()) for row in rows]
    return "\n".join(lines) + "\n"
```

**Why:** String concatenation in a loop is O(n²) because each `+` creates a new string; `str.join` is O(n).

---

### Using `Optional[X]` Instead of `X | None` in Modern Python

**Wrong:**
```python
from typing import Optional

def find_user(user_id: str) -> Optional[User]:
    ...
```

**Correct:**
```python
def find_user(user_id: str) -> User | None:
    ...
```

**Why:** `X | None` (PEP 604, Python 3.10+) is the idiomatic modern syntax; `Optional` requires a `typing` import and is now considered legacy style.

---

### Using `type(x) == SomeClass` Instead of `isinstance`

**Wrong:**
```python
def process(value):
    if type(value) == int:
        return value * 2
```

**Correct:**
```python
def process(value):
    if isinstance(value, int):
        return value * 2
```

**Why:** `type(x) == SomeClass` breaks for subclasses and does not respect the Python type hierarchy; `isinstance` handles inheritance correctly.

> For advanced patterns — concurrency (threading, multiprocessing, async/await), hexagonal architecture with FastAPI (full working code, Protocol ports, DI wiring, RFC 7807 error handling, tests), memory optimization, tooling (`pyproject.toml`, ruff, mypy), and anti-patterns — see skill: `python-patterns-advanced`.

Related Skills

zero-trust-patterns

8
from marvinrichter/clarc

Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening.

webrtc-patterns

8
from marvinrichter/clarc

WebRTC patterns — peer connection setup, ICE/STUN/TURN configuration, signaling server design, SFU vs mesh topology, screen sharing, media track management, and reconnect/ICE restart handling.

webhook-patterns

8
from marvinrichter/clarc

Webhook patterns for receiving, verifying (HMAC), and idempotently processing third-party events. Covers Stripe, GitHub, and generic webhook patterns, delivery guarantees, retry handling, and testing.

wasm-patterns

8
from marvinrichter/clarc

WebAssembly patterns: wasm-pack, wasm-bindgen (JS↔Wasm interop), WASI, Component Model, wasm-opt, Rust-to-WASM compilation, JS integration (web workers, streaming instantiation), and production deployment (CDN, Content-Type headers).

ux-micro-patterns

8
from marvinrichter/clarc

UX micro-patterns for every product state: Empty States, Loading States (skeleton screens, spinners, optimistic UI), Error States, Success States, Confirmation Dialogs, Onboarding Flows, and Progressive Disclosure. These patterns apply to every feature — done wrong, they're the biggest source of user confusion.

typescript-patterns

8
from marvinrichter/clarc

TypeScript patterns — type system best practices, strict mode, utility types, generics, discriminated unions, error handling with Result types, and module organization. Core patterns for production TypeScript.

typescript-patterns-advanced

8
from marvinrichter/clarc

Advanced TypeScript — mapped types, template literal types, conditional types, infer, type guards, decorators, async patterns, testing with Vitest/Jest, and performance. Extends typescript-patterns.

typescript-monorepo-patterns

8
from marvinrichter/clarc

TypeScript monorepo patterns with Turborepo + pnpm workspaces. Covers package structure, shared configs, task pipeline caching, build orchestration, and publishing strategy.

terraform-patterns

8
from marvinrichter/clarc

Infrastructure as Code with Terraform — project structure, remote state, modules, workspace strategy, AWS/GCP patterns, CI/CD integration, and security hardening. The standard for managing production infrastructure.

swiftui-patterns

8
from marvinrichter/clarc

SwiftUI architecture patterns, state management with @Observable, view composition, navigation, performance optimization, and modern iOS/macOS UI best practices.

swift-patterns

8
from marvinrichter/clarc

Core Swift patterns — value vs reference types, protocols, generics, optionals, Result, error handling, Codable, and module organization. Foundation for all Swift development.

swift-patterns-advanced

8
from marvinrichter/clarc

Advanced Swift patterns — property wrappers, result builders, Combine basics, opaque & existential types, macro system, advanced generics, and performance optimization. Extends swift-patterns.