python-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Python Best Practices

Python最佳实践

Type-First Development

类型优先开发

Types define the contract before implementation. Follow this workflow:
  1. Define data models - dataclasses, Pydantic models, or TypedDict first
  2. Define function signatures - parameter and return type hints
  3. Implement to satisfy types - let the type checker guide completeness
  4. Validate at boundaries - runtime checks where data enters the system
类型定义先于实现的契约。遵循以下工作流:
  1. 定义数据模型 - 优先使用dataclasses、Pydantic模型或TypedDict
  2. 定义函数签名 - 参数和返回值类型提示
  3. 按类型要求实现 - 让类型检查器引导代码完整性
  4. 在边界处验证 - 在数据进入系统的位置进行运行时检查

Make Illegal States Unrepresentable

让非法状态无法被表示

Use Python's type system to prevent invalid states at type-check time.
Dataclasses for structured data:
python
from dataclasses import dataclass
from datetime import datetime

@dataclass(frozen=True)
class User:
    id: str
    email: str
    name: str
    created_at: datetime

@dataclass(frozen=True)
class CreateUser:
    email: str
    name: str
利用Python的类型系统在类型检查阶段防止无效状态。
用于结构化数据的dataclasses:
python
from dataclasses import dataclass
from datetime import datetime

@dataclass(frozen=True)
class User:
    id: str
    email: str
    name: str
    created_at: datetime

@dataclass(frozen=True)
class CreateUser:
    email: str
    name: str

Frozen dataclasses are immutable - no accidental mutation

Frozen dataclasses是不可变的 - 避免意外修改


**Discriminated unions with Literal:**
```python
from dataclasses import dataclass
from typing import Literal

@dataclass
class Idle:
    status: Literal["idle"] = "idle"

@dataclass
class Loading:
    status: Literal["loading"] = "loading"

@dataclass
class Success:
    status: Literal["success"] = "success"
    data: str

@dataclass
class Failure:
    status: Literal["error"] = "error"
    error: Exception

RequestState = Idle | Loading | Success | Failure

def handle_state(state: RequestState) -> None:
    match state:
        case Idle():
            pass
        case Loading():
            show_spinner()
        case Success(data=data):
            render(data)
        case Failure(error=err):
            show_error(err)
NewType for domain primitives:
python
from typing import NewType

UserId = NewType("UserId", str)
OrderId = NewType("OrderId", str)

def get_user(user_id: UserId) -> User:
    # Type checker prevents passing OrderId here
    ...

def create_user_id(raw: str) -> UserId:
    return UserId(raw)
Enums for constrained values:
python
from enum import Enum, auto

class Role(Enum):
    ADMIN = auto()
    USER = auto()
    GUEST = auto()

def check_permission(role: Role) -> bool:
    match role:
        case Role.ADMIN:
            return True
        case Role.USER:
            return limited_check()
        case Role.GUEST:
            return False
    # Type checker warns if case is missing
Protocol for structural typing:
python
from typing import Protocol

class Readable(Protocol):
    def read(self, n: int = -1) -> bytes: ...

def process_input(source: Readable) -> bytes:
    # Accepts any object with a read() method
    return source.read()
TypedDict for external data shapes:
python
from typing import TypedDict, Required, NotRequired

class UserResponse(TypedDict):
    id: Required[str]
    email: Required[str]
    name: Required[str]
    avatar_url: NotRequired[str]

def parse_user(data: dict) -> UserResponse:
    # Runtime validation needed - TypedDict is structural
    return UserResponse(
        id=data["id"],
        email=data["email"],
        name=data["name"],
    )

**结合Literal的可区分联合:**
```python
from dataclasses import dataclass
from typing import Literal

@dataclass
class Idle:
    status: Literal["idle"] = "idle"

@dataclass
class Loading:
    status: Literal["loading"] = "loading"

@dataclass
class Success:
    status: Literal["success"] = "success"
    data: str

@dataclass
class Failure:
    status: Literal["error"] = "error"
    error: Exception

RequestState = Idle | Loading | Success | Failure

def handle_state(state: RequestState) -> None:
    match state:
        case Idle():
            pass
        case Loading():
            show_spinner()
        case Success(data=data):
            render(data)
        case Failure(error=err):
            show_error(err)
用于领域原语的NewType:
python
from typing import NewType

UserId = NewType("UserId", str)
OrderId = NewType("OrderId", str)

def get_user(user_id: UserId) -> User:
    # 类型检查器会阻止此处传入OrderId
    ...

def create_user_id(raw: str) -> UserId:
    return UserId(raw)
用于约束值的枚举:
python
from enum import Enum, auto

class Role(Enum):
    ADMIN = auto()
    USER = auto()
    GUEST = auto()

def check_permission(role: Role) -> bool:
    match role:
        case Role.ADMIN:
            return True
        case Role.USER:
            return limited_check()
        case Role.GUEST:
            return False
    # 若缺少分支,类型检查器会发出警告
用于结构类型的Protocol:
python
from typing import Protocol

class Readable(Protocol):
    def read(self, n: int = -1) -> bytes: ...

def process_input(source: Readable) -> bytes:
    # 接受任何带有read()方法的对象
    return source.read()
用于外部数据结构的TypedDict:
python
from typing import TypedDict, Required, NotRequired

class UserResponse(TypedDict):
    id: Required[str]
    email: Required[str]
    name: Required[str]
    avatar_url: NotRequired[str]

def parse_user(data: dict) -> UserResponse:
    # 需要运行时验证 - TypedDict是结构化的
    return UserResponse(
        id=data["id"],
        email=data["email"],
        name=data["name"],
    )

Module Structure

模块结构

Prefer smaller, focused files: one class or closely related set of functions per module. Split when a file handles multiple concerns or exceeds ~300 lines. Use
__init__.py
to expose public API; keep implementation details in private modules (
_internal.py
). Colocate tests in
tests/
mirroring the source structure.
优先使用小型、聚焦的文件:每个模块对应一个类或一组紧密相关的函数。当文件处理多个关注点或超过约300行时进行拆分。使用
__init__.py
暴露公共API;将实现细节放在私有模块(如
_internal.py
)中。测试代码放在
tests/
目录下,与源码结构保持一致。

Functional Patterns

函数式模式

  • Use list/dict/set comprehensions and generator expressions over explicit loops.
  • Prefer
    @dataclass(frozen=True)
    for immutable data; avoid mutable default arguments.
  • Use
    functools.partial
    for partial application; compose small functions over large classes.
  • Avoid class-level mutable state; prefer pure functions that take inputs and return outputs.
  • 优先使用列表/字典/集合推导式和生成器表达式,而非显式循环。
  • 优先使用
    @dataclass(frozen=True)
    定义不可变数据;避免可变默认参数。
  • 使用
    functools.partial
    进行部分应用;用多个小函数组合替代大型类。
  • 避免类级别的可变状态;优先使用纯函数(接受输入并返回输出)。

Instructions

注意事项

  • Raise descriptive exceptions for unsupported cases; every code path returns a value or raises. This makes failures debuggable and prevents silent corruption.
  • Propagate exceptions with context using
    from err
    ; catching requires re-raising or returning a meaningful result. Swallowed exceptions hide root causes.
  • Handle edge cases explicitly: empty inputs,
    None
    , boundary values. Include
    else
    clauses in conditionals where appropriate.
  • Use context managers for I/O; prefer
    pathlib
    and explicit encodings. Resource leaks cause production issues.
  • Add or adjust unit tests when touching logic; prefer minimal repros that isolate the failure.
  • 针对不支持的情况抛出描述性异常;每个代码路径要么返回值要么抛出异常。这会让故障更易于调试,防止静默数据损坏。
  • 使用
    from err
    传递异常上下文;捕获异常时需重新抛出或返回有意义的结果。被吞掉的异常会隐藏根本原因。
  • 显式处理边缘情况:空输入、
    None
    、边界值。在条件语句中适当添加
    else
    分支。
  • 使用上下文管理器处理I/O;优先使用
    pathlib
    和显式编码。资源泄漏会导致生产环境问题。
  • 修改逻辑时添加或调整单元测试;优先使用能隔离故障的最小复现用例。

Examples

示例

Explicit failure for unimplemented logic:
python
def build_widget(widget_type: str) -> Widget:
    raise NotImplementedError(f"build_widget not implemented for type: {widget_type}")
Propagate with context to preserve the original traceback:
python
try:
    data = json.loads(raw)
except json.JSONDecodeError as err:
    raise ValueError(f"invalid JSON payload: {err}") from err
Exhaustive match with explicit default:
python
def process_status(status: str) -> str:
    match status:
        case "active":
            return "processing"
        case "inactive":
            return "skipped"
        case _:
            raise ValueError(f"unhandled status: {status}")
Debug-level tracing with namespaced logger:
python
import logging

logger = logging.getLogger("myapp.widgets")

def create_widget(name: str) -> Widget:
    logger.debug("creating widget: %s", name)
    widget = Widget(name=name)
    logger.debug("created widget id=%s", widget.id)
    return widget
针对未实现逻辑的显式故障处理:
python
def build_widget(widget_type: str) -> Widget:
    raise NotImplementedError(f"build_widget not implemented for type: {widget_type}")
传递上下文以保留原始回溯信息:
python
try:
    data = json.loads(raw)
except json.JSONDecodeError as err:
    raise ValueError(f"invalid JSON payload: {err}") from err
带显式默认分支的穷尽匹配:
python
def process_status(status: str) -> str:
    match status:
        case "active":
            return "processing"
        case "inactive":
            return "skipped"
        case _:
            raise ValueError(f"unhandled status: {status}")
带命名空间日志器的调试级追踪:
python
import logging

logger = logging.getLogger("myapp.widgets")

def create_widget(name: str) -> Widget:
    logger.debug("creating widget: %s", name)
    widget = Widget(name=name)
    logger.debug("created widget id=%s", widget.id)
    return widget

Configuration

配置

  • Load config from environment variables at startup; validate required values before use. Missing config should fail immediately.
  • Define a config dataclass or Pydantic model as single source of truth; avoid
    os.getenv
    scattered throughout code.
  • Use sensible defaults for development; require explicit values for production secrets.
  • 在启动时从环境变量加载配置;使用前验证必填值。缺失配置应立即导致启动失败。
  • 定义一个配置dataclass或Pydantic模型作为单一可信源;避免在代码中分散使用
    os.getenv
  • 为开发环境设置合理默认值;生产环境的敏感信息需要显式配置。

Examples

示例

Typed config with dataclass:
python
import os
from dataclasses import dataclass

@dataclass(frozen=True)
class Config:
    port: int = 3000
    database_url: str = ""
    api_key: str = ""
    env: str = "development"

    @classmethod
    def from_env(cls) -> "Config":
        database_url = os.environ.get("DATABASE_URL", "")
        if not database_url:
            raise ValueError("DATABASE_URL is required")
        return cls(
            port=int(os.environ.get("PORT", "3000")),
            database_url=database_url,
            api_key=os.environ["API_KEY"],  # required, will raise if missing
            env=os.environ.get("ENV", "development"),
        )

config = Config.from_env()
带类型的dataclass配置:
python
import os
from dataclasses import dataclass

@dataclass(frozen=True)
class Config:
    port: int = 3000
    database_url: str = ""
    api_key: str = ""
    env: str = "development"

    @classmethod
    def from_env(cls) -> "Config":
        database_url = os.environ.get("DATABASE_URL", "")
        if not database_url:
            raise ValueError("DATABASE_URL is required")
        return cls(
            port=int(os.environ.get("PORT", "3000")),
            database_url=database_url,
            api_key=os.environ["API_KEY"],  # 必填项,缺失时会抛出异常
            env=os.environ.get("ENV", "development"),
        )

config = Config.from_env()

Optional: ty

可选工具:ty

For fast type checking, consider ty from Astral (creators of ruff and uv). Written in Rust, it's significantly faster than mypy or pyright.
Installation and usage:
bash
undefined
如需快速类型检查,可考虑使用Astral(ruff和uv的开发者)推出的ty。它由Rust编写,速度显著快于mypy或pyright。
安装与使用:
bash
undefined

Run directly with uvx (no install needed)

使用uvx直接运行(无需安装)

uvx ty check
uvx ty check

Check specific files

检查特定文件

uvx ty check src/main.py
uvx ty check src/main.py

Install permanently

永久安装

uv tool install ty

**Key features:**
- Automatic virtual environment detection (via `VIRTUAL_ENV` or `.venv`)
- Project discovery from `pyproject.toml`
- Fast incremental checking
- Compatible with standard Python type hints

**Configuration in `pyproject.toml`:**
```toml
[tool.ty]
python-version = "3.12"
When to use ty vs alternatives:
  • ty
    - fastest, good for CI and large codebases (early stage, rapidly evolving)
  • pyright
    - most complete type inference, VS Code integration
  • mypy
    - mature, extensive plugin ecosystem
uv tool install ty

**核心特性:**
- 自动检测虚拟环境(通过`VIRTUAL_ENV`或`.venv`)
- 从`pyproject.toml`发现项目
- 快速增量检查
- 兼容标准Python类型提示

**在`pyproject.toml`中配置:**
```toml
[tool.ty]
python-version = "3.12"
ty与其他工具的选择场景:
  • ty
    - 速度最快,适合CI和大型代码库(早期阶段,快速演进)
  • pyright
    - 类型推断最完整,支持VS Code集成
  • mypy
    - 成熟稳定,拥有丰富的插件生态