Python类型优先开发最佳实践 python-best-practices

本技能提供Python类型优先开发的完整指南,涵盖数据类、判别联合类型、NewType、协议等核心概念,旨在通过静态类型检查提升代码质量、可维护性和可靠性。关键词:Python类型提示,静态类型检查,数据类,Pydantic,mypy,pyright,代码质量,软件工程最佳实践。

后端开发 0 次安装 0 次浏览 更新于 2/23/2026

name: python最佳实践 description: 提供面向类型优先开发的Python模式,包括数据类、判别联合类型、NewType和协议。在读写Python文件时必须使用。

Python最佳实践

类型优先开发

类型在实现之前定义契约。遵循以下工作流程:

  1. 定义数据模型 - 首先定义数据类、Pydantic模型或TypedDict
  2. 定义函数签名 - 参数和返回类型提示
  3. 实现以满足类型 - 让类型检查器指导完整性
  4. 在边界处验证 - 在数据进入系统时进行运行时检查

使非法状态无法表示

使用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

# 冻结的数据类是不可变的 - 防止意外修改

使用Literal的判别联合类型:

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:

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)

用于约束值的枚举:

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
    # 如果缺少case,类型检查器会发出警告

用于结构类型化的协议:

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:

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"],
    )

模块结构

优先使用更小、更专注的文件:每个模块一个类或一组紧密相关的函数。当文件处理多个关注点或超过约300行时进行拆分。使用__init__.py来暴露公共API;将实现细节保留在私有模块中(_internal.py)。将测试放在tests/中,镜像源结构。

函数式模式

  • 使用列表/字典/集合推导式和生成器表达式,而不是显式循环。
  • 优先使用@dataclass(frozen=True)处理不可变数据;避免可变默认参数。
  • 使用functools.partial进行部分应用;组合小函数而不是大型类。
  • 避免类级别的可变状态;优先使用接受输入并返回输出的纯函数。

指令

  • 对于不支持的情况引发描述性异常;每个代码路径都应返回值或引发异常。这使故障可调试并防止静默损坏。
  • 使用from err传播带有上下文的异常;捕获时需要重新引发或返回有意义的结果。吞掉的异常会隐藏根本原因。
  • 显式处理边缘情况:空输入、None、边界值。在条件语句中适当包含else子句。
  • 对I/O使用上下文管理器;优先使用pathlib和显式编码。资源泄漏会导致生产问题。
  • 在接触逻辑时添加或调整单元测试;优先使用隔离故障的最小复现案例。

示例

未实现逻辑的显式失败:

def build_widget(widget_type: str) -> Widget:
    raise NotImplementedError(f"build_widget not implemented for type: {widget_type}")

传播上下文以保留原始回溯:

try:
    data = json.loads(raw)
except json.JSONDecodeError as err:
    raise ValueError(f"invalid JSON payload: {err}") from err

带有显式默认值的穷举匹配:

def process_status(status: str) -> str:
    match status:
        case "active":
            return "processing"
        case "inactive":
            return "skipped"
        case _:
            raise ValueError(f"unhandled status: {status}")

使用命名空间记录器的调试级别跟踪:

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

配置

  • 在启动时从环境变量加载配置;在使用前验证必需值。缺少配置应立即失败。
  • 将配置数据类或Pydantic模型定义为单一事实来源;避免在代码中分散使用os.getenv
  • 为开发使用合理的默认值;对生产机密要求显式值。

示例

使用数据类的类型化配置:

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()

可选:ty

对于快速类型检查,可以考虑来自Astral(ruff和uv的创建者)的ty。用Rust编写,比mypy或pyright快得多。

安装和使用:

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

# 检查特定文件
uvx ty check src/main.py

# 永久安装
uv tool install ty

关键特性:

  • 自动虚拟环境检测(通过VIRTUAL_ENV.venv
  • pyproject.toml发现项目
  • 快速增量检查
  • 兼容标准Python类型提示

pyproject.toml中的配置:

[tool.ty]
python-version = "3.12"

何时使用ty与替代方案:

  • ty - 最快,适用于CI和大型代码库(早期阶段,快速发展)
  • pyright - 最完整的类型推断,VS Code集成
  • mypy - 成熟,广泛的插件生态系统