name: python最佳实践 description: 提供面向类型优先开发的Python模式,包括数据类、判别联合类型、NewType和协议。在读写Python文件时必须使用。
Python最佳实践
类型优先开发
类型在实现之前定义契约。遵循以下工作流程:
- 定义数据模型 - 首先定义数据类、Pydantic模型或TypedDict
- 定义函数签名 - 参数和返回类型提示
- 实现以满足类型 - 让类型检查器指导完整性
- 在边界处验证 - 在数据进入系统时进行运行时检查
使非法状态无法表示
使用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- 成熟,广泛的插件生态系统