Python 3.10+ best practices, patterns, and idioms. Covers type hints, async, dataclasses, protocols, and modern Python tooling. Use when writing Python code, reviewing Python patterns, or learning idiomatic Python.
Install
npx skillscat add peopleforrester/claude-dotfiles/python-patterns Install via the SkillsCat registry.
SKILL.md
Python Patterns
Modern Python 3.10+ patterns and best practices.
Type Hints
Strict Typing
from typing import TypeVar, Protocol, Optional
from collections.abc import Sequence
T = TypeVar('T')
def first_or_none(items: Sequence[T]) -> Optional[T]:
return items[0] if items else NoneProtocols (Structural Typing)
from typing import Protocol, runtime_checkable
@runtime_checkable
class Renderable(Protocol):
def render(self) -> str: ...
class HtmlWidget:
def render(self) -> str:
return "<div>widget</div>"
def display(item: Renderable) -> None:
print(item.render())
display(HtmlWidget()) # Works - structural matchTypedDict for Structured Data
from typing import TypedDict, NotRequired
class UserConfig(TypedDict):
name: str
email: str
theme: NotRequired[str] # Optional keyDataclasses and Immutability
Frozen Dataclasses
from dataclasses import dataclass, field
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: float
def distance_to(self, other: "Point") -> float:
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5Pydantic for Validation
from pydantic import BaseModel, Field, field_validator
class CreateUser(BaseModel):
name: str = Field(min_length=1, max_length=100)
email: str
age: int = Field(ge=0, le=150)
@field_validator('email')
@classmethod
def validate_email(cls, v: str) -> str:
if '@' not in v:
raise ValueError('Invalid email')
return v.lower()Async Patterns
Structured Concurrency
import asyncio
async def fetch_all(urls: list[str]) -> list[str]:
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch(url)) for url in urls]
return [t.result() for t in tasks]Async Context Managers
from contextlib import asynccontextmanager
from typing import AsyncIterator
@asynccontextmanager
async def db_transaction() -> AsyncIterator[Connection]:
conn = await get_connection()
try:
yield conn
await conn.commit()
except Exception:
await conn.rollback()
raise
finally:
await conn.close()Error Handling
Result Pattern
from dataclasses import dataclass
from typing import Generic, TypeVar, Union
T = TypeVar('T')
E = TypeVar('E', bound=Exception)
@dataclass(frozen=True)
class Ok(Generic[T]):
value: T
@dataclass(frozen=True)
class Err(Generic[E]):
error: E
Result = Union[Ok[T], Err[E]]
def divide(a: float, b: float) -> Result[float, ValueError]:
if b == 0:
return Err(ValueError("Division by zero"))
return Ok(a / b)Match Statements (3.10+)
match command.split():
case ["quit"]:
sys.exit(0)
case ["go", direction]:
move(direction)
case ["get", item] if item in inventory:
pick_up(item)
case _:
print(f"Unknown command: {command}")Tooling
| Tool | Purpose | Command |
|---|---|---|
| uv | Package management | uv add package |
| ruff | Linting + formatting | ruff check . && ruff format . |
| mypy | Type checking | mypy src/ --strict |
| pytest | Testing | pytest -xvs |
| pyright | Type checking (fast) | pyright src/ |
Checklist
- Type hints on all public functions
- Dataclasses for data containers (not plain dicts)
- Async for I/O-bound operations
- Match statements for complex branching (3.10+)
- Protocols for duck typing
- Pydantic for input validation
- uv for package management
- ruff for linting and formatting