Python 3.10+ 的 typing 已能表达绝大多数业务约束。工程上的关键不是"写不写类型",而是默认开启 mypy 严格模式,让类型错误在 CI 阶段暴露,而非线上 AttributeError。
from typing import Protocol, TypeVar
from dataclasses import dataclass
T = TypeVar("T")
class Repository(Protocol[T]): # 结构化子类型,不强制继承
def get(self, key: str) -> T: ...
def put(self, key: str, value: T) -> None: ...
@dataclass(frozen=True, slots=True) # 3.10+:不可变 + 省内存
class User:
uid: int
name: str
tags: tuple[str, ...] = ()
def load(repo: Repository[User], key: str) -> User:
return repo.get(key)Protocol 让依赖倒置不依赖继承;frozen=True 保证可哈希;slots=True 对高频创建对象可省约 30% 内存。mypy 建议 strict = true + disallow_untyped_defs,新项目一步到位,老项目用 ignore_missing_imports 逐模块收敛。
框架能力最终落在数据模型上。下面这段组合了上下文管理器、迭代协议与运算符重载,是"可组合"设计的最小样本:
class Batch:
def __init__(self, rows):
self._rows = list(rows)
def __len__(self): return len(self._rows)
def __iter__(self): return iter(self._rows)
def __getitem__(self, i): return self._rows[i]
def __enter__(self): return self
def __exit__(self, *exc):
self._rows.clear()
return False # 不吞异常,多数场景的正确选择
def __repr__(self): return f"Batch(n={len(self)})"实现了 __iter__ 就获得 for、in、list() 等全部消费方式;这是"用协议换兼容"的典型收益。
优化顺序是固定的:profile → 判断瓶颈类型 → 选工具。纯 Python 循环慢,但把热点交给 numpy 向量化通常比写 C 扩展更划算:
import numpy as np, timeit
def py_loop(xs): return sum(x * x for x in xs)
def np_vec(xs): return float((np.asarray(xs) ** 2).sum())
data = list(range(1_000_000))
print(timeit.timeit(lambda: py_loop(data), number=3))
print(timeit.timeit(lambda: np_vec(data), number=3)) # 通常快 1~2 个数量级只有 numpy 无法表达、且 cProfile 确认热点占比 >20% 时,才考虑 Cython 或 PyO3。工具选型必须由 py-spy、cProfile 的数据支撑,不能凭直觉。
pyproject.toml 是唯一真源setup.py 时代的多处配置已过时。现代项目用一个文件声明元数据、依赖与工具配置:
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "mylib"
version = "1.2.0"
requires-python = ">=3.10"
dependencies = ["numpy>=1.24"]
[project.optional-dependencies]
dev = ["pytest", "mypy", "ruff", "hypothesis"]
[tool.mypy]
strict = true
[tool.ruff]
line-length = 100库的依赖用兼容范围(>=),部署环境用 requirements 锁定精确版本——二者职责不同,不可混用。
pytest 参数化解决"样例覆盖",hypothesis 解决"边界想不到",二者互补:
import pytest
from hypothesis import given, strategies as st
def fact(n: int) -> int:
return 1 if n <= 1 else n * fact(n - 1)
@pytest.mark.parametrize("n,expected", [(0, 1), (1, 1), (5, 120)])
def test_fact(n, expected):
assert fact(n) == expected
@given(st.lists(st.integers(), min_size=1))
def test_max_in_list(xs):
assert max(xs) in xs # 属性:结果必属于输入属性测试尤其适合序列化往返一致性、排序稳定性这类"不变量"校验——它们用随机输入代替人工枚举,能稳定复现边界缺陷。
"Python 全系列"不是罗列库,而是建立一条类型契约 → 数据模型 → 性能决策 → 打包交付 → 测试兜底的闭环。四条准则足以覆盖多数工程判断:类型默认真、dunder 用对、优化先 profile、配置单一真源。把这条链路固化进 CI(ruff + mypy + pytest),代码的可维护性就不再依赖个人经验,而成为流水线可保证的交付属性。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。