Claude
Skills
Sign in
Back

python-3-13-patterns

Included with Lifetime
$97 forever

Modern Python 3.13 development patterns and syntax including async/await, pattern matching, and modern type annotations. PROACTIVELY activate for: (1) Writing new Python 3.13 modules, (2) Implementing async/concurrent operations, (3) Using structural pattern matching (match/case), (4) Creating decorators or context managers, (5) Working with generators. Triggers: "python patterns", "match case", "async await", "context manager", "decorator", "generator", "python 3.13"

Writing & Docs

What this skill does


# Python 3.13 Modern Patterns

## Core Principles

Python 3.13 represents the culmination of modern Python design. This skill covers essential patterns for writing clean, efficient, and maintainable Python code using the latest language features.

## Modern Type Annotations (Built-in Generics)

Python 3.9+ allows using built-in types for generic annotations. This is the **required** pattern for all new code:

```python
# ✅ REQUIRED: Use built-in types
def process_items(items: list[str]) -> dict[str, int]:
    return {item: len(item) for item in items}

def merge_data(data1: dict[str, any], data2: dict[str, any]) -> dict[str, any]:
    return {**data1, **data2}

# ❌ FORBIDDEN: Legacy typing module imports
from typing import List, Dict  # Don't use these!
def process_items(items: List[str]) -> Dict[str, int]:  # Old style
    pass
```

## Union Types with | Operator

Use the pipe operator for union types. This is cleaner and more Pythonic:

```python
# ✅ REQUIRED: Use | for unions
def get_user(user_id: int) -> User | None:
    """Returns User or None if not found."""
    return db.query(User).get(user_id)

def process_value(value: str | int | float) -> str:
    """Handles multiple types."""
    return str(value)

# ✅ REQUIRED: None always comes last in unions
def find_item(query: str) -> Item | None:
    pass

# ❌ FORBIDDEN: Legacy Optional and Union
from typing import Optional, Union
def get_user(user_id: int) -> Optional[User]:  # Old style
    pass
def process_value(value: Union[str, int, float]) -> str:  # Old style
    pass
```

## Structural Pattern Matching (match/case)

Use match/case for complex conditional logic based on structure:

```python
# ✅ REQUIRED: Use match/case for complex conditionals
def handle_command(command: str) -> str:
    match command.split():
        case ["quit"]:
            return "Exiting..."
        case ["load", filename]:
            return f"Loading {filename}"
        case ["save", filename]:
            return f"Saving to {filename}"
        case ["set", key, value]:
            return f"Setting {key} = {value}"
        case _:
            return "Unknown command"

# Pattern matching with type checking
def process_message(message: dict) -> str:
    match message:
        case {"type": "user_created", "user_id": user_id, "email": email}:
            return f"Welcome user {user_id}: {email}"
        case {"type": "user_deleted", "user_id": user_id}:
            return f"User {user_id} deleted"
        case {"type": "error", "code": code, "message": msg}:
            raise ValueError(f"Error {code}: {msg}")
        case _:
            return "Unknown message type"

# Matching with guards
def categorize_number(n: int) -> str:
    match n:
        case 0:
            return "zero"
        case n if n < 0:
            return "negative"
        case n if n > 0 and n < 10:
            return "small positive"
        case n if n >= 10:
            return "large positive"
```

## Async/Await Patterns

Async functions are first-class citizens in Python 3.13:

```python
import asyncio

# ✅ REQUIRED: Comprehensive error handling in async functions
async def fetch_data(url: str) -> dict[str, any]:
    """Fetch data with proper error handling and resource cleanup."""
    async with aiohttp.ClientSession() as session:
        try:
            async with session.get(url, timeout=10) as response:
                response.raise_for_status()
                return await response.json()
        except asyncio.TimeoutError:
            raise TimeoutError(f"Request to {url} timed out")
        except aiohttp.ClientError as e:
            raise ConnectionError(f"Failed to fetch {url}: {e}")

# ✅ REQUIRED: Use asyncio.gather for concurrent operations
async def fetch_multiple(urls: list[str]) -> list[dict[str, any]]:
    """Fetch multiple URLs concurrently."""
    tasks = [fetch_data(url) for url in urls]
    return await asyncio.gather(*tasks, return_exceptions=True)

# ✅ REQUIRED: Use async context managers for resources
class AsyncDatabaseConnection:
    async def __aenter__(self):
        self.conn = await connect_to_db()
        return self.conn

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.conn.close()

async def query_database():
    async with AsyncDatabaseConnection() as conn:
        return await conn.execute("SELECT * FROM users")
```

## Context Managers

Context managers ensure proper resource cleanup:

```python
from contextlib import contextmanager
from typing import Generator

# ✅ REQUIRED: Use context managers for resource management
@contextmanager
def open_file_safely(filename: str, mode: str = 'r') -> Generator[any, None, None]:
    """Context manager for file operations with error handling."""
    file = None
    try:
        file = open(filename, mode)
        yield file
    except IOError as e:
        print(f"Error opening {filename}: {e}")
        raise
    finally:
        if file:
            file.close()

# Class-based context manager
class DatabaseTransaction:
    def __init__(self, connection):
        self.connection = connection

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

    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) as db:
    db.execute("INSERT INTO users VALUES (...)")
```

## Decorators

Decorators are powerful for cross-cutting concerns:

```python
from functools import wraps
from typing import Callable, Any
import time

# ✅ REQUIRED: Preserve function metadata with @wraps
def timing_decorator(func: Callable) -> Callable:
    """Measure execution time of functions."""
    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} took {end - start:.4f}s")
        return result
    return wrapper

# Decorator with arguments (decorator factory)
def retry(max_attempts: int = 3, delay: float = 1.0):
    """Retry decorator with configurable attempts and delay."""
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    time.sleep(delay)
            return None
        return wrapper
    return decorator

# Usage
@retry(max_attempts=5, delay=2.0)
def fetch_api_data(url: str) -> dict:
    """Fetch data with automatic retries."""
    pass
```

## Generators

Generators enable memory-efficient data processing:

```python
from typing import Generator

# ✅ REQUIRED: Use generators for large datasets
def read_large_file(filename: str) -> Generator[str, None, None]:
    """Read file line by line without loading into memory."""
    with open(filename, 'r') as file:
        for line in file:
            yield line.strip()

def process_data_stream(data: list[int]) -> Generator[int, None, None]:
    """Process data in chunks."""
    for item in data:
        if item > 0:
            yield item * 2

# Generator with send() for coroutine-like behavior
def running_average() -> Generator[float, float, None]:
    """Calculate running average of sent values."""
    total = 0.0
    count = 0
    while True:
        value = yield (total / count) if count > 0 else 0.0
        total += value
        count += 1

# Usage
avg = running_average()
next(avg)  # Prime the generator
print(avg.send(10))  # 10.0
print(avg.send(20))  # 15.0
print(avg.send(30))  # 20.0
```

## Anti-Patterns to Avoid

### Using Legacy typing Imports
```python
# BAD
from typing import List, Dict,

Related in Writing & Docs