python-patterns
GitHub提供Python最佳实践指南,涵盖类型提示、异步模式、测试策略及项目结构。遵循显式优于隐式、组合优于继承等核心原则,指导开发者构建类型安全、可测试且易维护的Python代码,目标达到生产级标准。
Trigger Scenarios
Install
npx skills add cosmicstack-labs/mercury-agent-skills --skill python-patterns -g -y
SKILL.md
Frontmatter
{
"name": "python-patterns",
"metadata": {
"tags": [
"python",
"type-hints",
"async",
"testing",
"best-practices"
],
"author": "cosmicstack-labs",
"version": "1.0.0",
"category": "backend"
},
"description": "Python best practices including type hints, async patterns, testing, and project structure"
}
Python Patterns
Write Python that is type-safe, testable, and a joy to maintain.
Core Principles
1. Explicit Over Implicit
Use type hints. Avoid *args and **kwargs when named parameters work. Favor clear interfaces over dynamic flexibility.
2. Composition Over Inheritance
Python's multiple inheritance is powerful but dangerous. Prefer composition and protocols over deep class hierarchies.
3. Async Done Right
Async is a tool for I/O-bound workloads, not a universal default. Use synchronous code for CPU-bound tasks, async for network calls and file I/O.
4. Test-First for Critical Paths
Your business logic should be testable without mocks. Use dependency injection. Keep I/O at the boundaries.
Python Maturity Model
| Level | Typing | Async | Testing | Structure |
|---|---|---|---|---|
| 1: Script | No type hints | sync only | Manual testing | Single file |
| 2: Module | Basic types (str, int) | Basic asyncio | pytest, some coverage | Package with __init__.py |
| 3: Package | Full type hints with mypy | Async with proper patterns | pytest + fixtures + mocking | src-layout, entry points |
| 4: Service | Generics, Protocols, TypedDict | Structured concurrency | Property-based, integration tests | Domain-driven structure |
| 5: Library | Precise types, variance annotations | Trio / anyio | Fuzzing, benchmark tests | Public API surface explicit |
Target: Level 3+ for production services.
Actionable Guidance
Type Hints
Basic Patterns
from typing import Optional, Union, Sequence, TypeVar, Protocol, Any
from datetime import datetime
# Function signatures
def process_user(
user_id: int,
name: str,
email: Optional[str] = None,
tags: list[str] | None = None, # Python 3.10+ union syntax
) -> dict[str, Any]:
...
# TypedDict for structured dicts
class UserData(TypedDict, total=False):
id: int
name: str
email: str
created_at: datetime
# Protocols (structural subtyping)
class Drawable(Protocol):
def draw(self, context: Any) -> None: ...
def render(item: Drawable) -> None:
item.draw(...) # Any object with draw() method works
Generic Types
T = TypeVar('T')
U = TypeVar('U', bound=Comparable)
class Repository(Generic[T]):
def get(self, id: int) -> T | None: ...
def list(self) -> Sequence[T]: ...
def save(self, item: T) -> T: ...
Async Patterns
Proper Async Context Managers
import asyncio
from contextlib import asynccontextmanager
@asynccontextmanager
async def db_session():
session = await create_session()
try:
yield session
finally:
await session.close()
# Usage
async with db_session() as session:
result = await session.query(...)
Structured Concurrency
async def fetch_all_data():
# Run tasks concurrently with proper error propagation
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch_users())
task2 = tg.create_task(fetch_orders())
task3 = tg.create_task(fetch_products())
# All tasks completed (or TaskGroup raised on error)
return task1.result(), task2.result(), task3.result()
Timeouts and Cancellation
async def fetch_with_timeout(url: str, timeout: float = 10.0) -> Response:
try:
async with asyncio.timeout(timeout):
return await fetch(url)
except TimeoutError:
logger.warning(f"Request to {url} timed out after {timeout}s")
raise ServiceUnavailableError(f"Timeout fetching {url}")
Project Structure
Recommended: src layout
project/
├── pyproject.toml
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── domain/ # Business logic
│ │ ├── models.py
│ │ └── services.py
│ ├── infrastructure/ # External dependencies
│ │ ├── database.py
│ │ └── http_client.py
│ ├── api/ # Entry points
│ │ └── routes.py
│ └── config.py
├── tests/
│ ├── unit/
│ ├── integration/
│ └── conftest.py
└── README.md
pyproject.toml (modern Python packaging):
[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"
[project]
name = "mypackage"
version = "0.1.0"
dependencies = [
"fastapi>=0.100",
"pydantic>=2.0",
]
Testing Patterns
Fixtures for Clean Tests
import pytest
from datetime import datetime, timezone
@pytest.fixture
def sample_user() -> UserData:
return UserData(
id=1,
name="Alice",
email="alice@example.com",
created_at=datetime.now(timezone.utc),
)
@pytest.fixture
def repo(in_memory_db):
return UserRepository(in_memory_db)
def test_create_user(repo, sample_user):
saved = repo.save(sample_user)
assert saved.id == 1
assert saved.name == "Alice"
def test_get_nonexistent_user(repo):
result = repo.get(999)
assert result is None
Testing Async Code
@pytest.mark.asyncio
async def test_async_service():
service = UserService(client=MockAsyncClient())
result = await service.get_user(42)
assert result.name == "Alice"
Property-Based Testing
from hypothesis import given, strategies as st
@given(st.integers(min_value=1, max_value=1000))
def test_user_id_is_positive(user_id: int):
result = process_user(user_id)
assert result["user_id"] > 0
@given(st.emails())
def test_valid_email_format(email: str):
assert validate_email(email) is True
Error Handling
Custom Exception Hierarchy
class AppError(Exception):
"""Base exception for application errors."""
def __init__(self, message: str, code: str | None = None):
super().__init__(message)
self.code = code or "UNKNOWN_ERROR"
class NotFoundError(AppError):
def __init__(self, resource: str, id: int | str):
super().__init__(f"{resource} not found: {id}", code="NOT_FOUND")
class ValidationError(AppError):
def __init__(self, message: str, field: str | None = None):
super().__init__(message, code="VALIDATION_ERROR")
self.field = field
Result Pattern (Alternative to Exceptions)
from dataclasses import dataclass
from typing import Generic, TypeVar
T = TypeVar('T')
E = TypeVar('E')
@dataclass
class Ok(Generic[T]):
value: T
@dataclass
class Err(Generic[E]):
error: E
Result = Ok[T] | Err[E]
def get_user(user_id: int) -> Result[User, AppError]:
user = database.find(user_id)
if user is None:
return Err(NotFoundError("User", user_id))
return Ok(user)
Common Mistakes
- Mutable default arguments:
def func(items=[])— creates one list shared across calls. UseNoneand initialize inside. - Ignoring type hints in hot paths: Type hints have minimal runtime cost but catch bugs early. Use mypy/pyright in CI.
- Blocking the event loop: Calling
requests.get()inside async code blocks all coroutines. Usehttpx.AsyncClient. - Overusing
**kwargs: Pass-through kwargs obscure function signatures. Be explicit about parameters. - Not using
__slots__: For classes with many instances,__slots__reduces memory by ~50%. - Mixing sync and async carelessly: Calling async from sync (or vice versa) requires careful handling. Use
asyncio.run()only at entry points. - Deep nested context managers: Chain too many
async withblocks. Extract into helper methods or context manager composition.
Version History
- 38e2523 Current 2026-07-05 19:37


