关于 python-typing
为 Python 包引入静态类型检查的技能,适用于新项目和遗留代码团队。内容涵盖检查器选择、固定版本与配置、逐步提高严格度、错误处理,以及验证库的类型标记是否进入构建产物。
主要功能
- 01选择一个主要类型检查器
- 02固定检查器版本并配置 pyproject.toml
- 03逐步提高严格度并跟踪豁免
- 04处理类型错误与忽略项
- 05验证构建产物中的 py.typed
适用场景
- 01为新的 Python 包配置类型检查
- 02逐步为遗留代码引入检查
- 03为下游用户准备库的类型标注
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ paldom/ python-skills --skill python-typing安装后在 Claude Code 中输入/python-typing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/checker-selection.mdpy-typed-distribution.mdstrictness-ratchet.mdscripts/check_py_typed.py详细介绍
Python Typing
在用户的 Python 包中启用静态类型检查,并采用团队能够说明理由的严格度策略:只选一个检查器,固定版本,在 pyproject.toml 中配置,跟踪每个模块的临时豁免并将其视为技术债,逐步提高严格度;对于库,还要添加 py.typed 标记,并验证它确实包含在构建出的 wheel 中。本节还介绍如何修复检查器报告的错误。
不适用场景
- Ruff lint 或格式检查失败——包括 ANN 注解风格规则,这些属于 lint,而不是类型检查(如果已安装,请使用 python-lint skill)。
- 测试套件失败或 pytest 配置问题(python-testing)。
- 运行时异常——运行时抛出的
TypeError是需要直接调试的 bug,不是检查器配置问题。 - 运行检查器的 CI 工作流 YAML(python-ci)或 pre-commit 配置(python-precommit)——本 skill 会将可用的命令交给它们。
- 除
py.typed标记本身以外的打包元数据、构建后端或 wheel 构建问题(python-packaging)。
工作流
1. 选择前先摸清情况
- 项目布局:
src/还是扁平布局;导入包名称;是库(已发布,有使用者)还是应用程序。 requires-python声明的最低支持 Python 版本——检查器必须以它为目标,而不是你当前运行的解释器。- 需要检查器 插件 的框架依赖:
grep -E "django|sqlalchemy|pydantic" pyproject.toml。只有 mypy 有这些插件;这可能决定整个检查器的选择。 - 现有配置(
[tool.mypy]、[tool.pyright]、mypy.ini、pyrightconfig.json)——在其基础上扩展,不要悄悄替换。 - 新项目还是遗留代码库(大致的注解覆盖率)——这决定第 4 步采用从一开始就严格检查,还是逐步收紧。
2. 只选一个检查器——竞争格局尚未明朗
类型检查器领域的竞争仍在进行,尚无定论(mypy、pyright/basedpyright、ty、Pyrefly、zuban)。不要把其中任何一个说成显而易见的赢家,也不要引用速度倍数或符合度百分比——流传的数字都来自单一来源,且部分已被反驳。以下选择规则经得起推敲:
| 情况 | 选择 |
|---|---|
| Django(django-stubs)或依赖 Pydantic 插件的技术栈 | mypy + 框架插件(SQLAlchemy 2.x 原生支持类型;其 mypy 插件已弃用,且仅支持到 mypy 1.10) |
| 新项目、无需插件、团队使用 VS Code | pyright(先用 standard,再用 strict) |
| 使用 VS Code 以外的编辑器(Neovim、Helix、Cursor),或希望默认设置更严格 | basedpyright |
| 使用 uv/Ruff 且能接受 beta 版本的变动 | 在本地试用 ty;CI 门禁继续使用 mypy 或 pyright |
| 数百万行代码的单体仓库 | 评估 Pyrefly |
选定一个主要检查器作为 CI 门禁。在编辑器中使用 pyright(Pylance),同时在 CI 中使用另一个检查器很常见;将两个检查器都设为门禁,意味着要维护两套并行的抑制注释(注意事项 5)。详情和权衡:references/checker-selection.md。
3. 固定版本并通过 uv 运行
固定确切版本——检查器发布新版本时通常会增加新的诊断项,因此不固定版本会导致 CI 因无关 PR 而失败。将以下示例中的版本替换为 PyPI 上的当前版本:
uv add --dev "mypy==2.3.1" # or: "pyright==1.1.413" (PyPI wrapper, bundles node)
uv run mypy src tests # or: uv run pyright
不使用 uv 时(仅供参考):先运行 python -m pip install "mypy==2.3.1",再运行
python -m mypy src tests。若要试用 ty 而不改动依赖:
uvx --from "ty==0.0.80" ty check src(同样,请替换为当前固定版本;ty 仍处于 1.0 之前)。
4. 在 pyproject.toml 中配置
mypy 和 pyright 读取不同的配置表,因此迁移期间二者可以共存。mypy——请注意,只有设置 strict = true,mypy 才会检查未添加注解的代码(注意事项 1):
[tool.mypy]
python_version = "3.10" # lowest supported, from requires-python
strict = true
warn_unused_ignores = true
# plugins = ["pydantic.mypy"] # only if pydantic is a dependency
[[tool.mypy.overrides]]
module = ["some_untyped_lib.*"] # third-party deps that ship no types
ignore_missing_imports = true
pyright / basedpyright(使用 [tool.basedpyright] 配置 fork):
[tool.pyright]
include = ["src", "tests"]
exclude = ["**/legacy"] # added on top of pyright's defaults (node_modules, __pycache__, .*)
typeCheckingMode = "standard" # "strict" for new projects
pythonVersion = "3.10"
ty 会读取 pyproject.toml 中的 [tool.ty](或 ty.toml);其配置选项较少,且仍处于 beta 阶段——请查看 uv run ty --help,不要轻信过时示例。
5. 设定严格程度——从第一天开始,或逐步收紧,绝不要一次性改造
- 新项目:从第一次提交起就设为
strict = true/typeCheckingMode = "strict"。事后再改为严格模式,才会造成贡献者摩擦和成百上千条错误。 - 遗留代码库:逐步收紧。全局启用严格模式,并按模块设置豁免,且豁免范围只能缩小:
[tool.mypy]
strict = true
[[tool.mypy.overrides]]
module = ["myapp.legacy.*"] # exempt, migrate module-by-module
ignore_errors = true
pyright 的对应做法是基于 standard 基础配置,使用 strict = ["src/myapp/core"] 数组设置严格检查的模块。两种检查器的完整分阶段推广方案见:references/strictness-ratchet.md。
- 在团队能看到的地方(CONTRIBUTING 或 README)记录策略:所选的检查器、运行命令,以及豁免范围只能缩小这一规则。
6. 发布 py.typed 并验证 wheel(适用于库)
没有 py.typed 标记时,根据 PEP 561,检查器会将库中的所有导入都视为 Any——源码中虽然有类型注解,使用者却看不到,而且不会收到任何提示。
touch src/<pkg>/py.typed
不同构建后端对是否打包非 .py 文件的处理不同(setuptools 需要 [tool.setuptools.package-data];hatchling 通常会将其包含在内),因此绝不要只相信源码树——验证构建产物:
uv build
python3 "${CLAUDE_SKILL_DIR}/scripts/check_py_typed.py" --package <pkg>
如果源码包、最新的 wheel 或最新的 sdist 中缺少 py.typed,脚本就会以非零状态码退出。各构建后端的配置和存根分发选项见:references/py-typed-distribution.md。
7. 修复报告的错误
按以下顺序分类处理:
- 优先修复真实错误——误用
Optional、返回类型错误、无法到达的分支。这些修复最有价值;不要抑制这类错误。 - 没有类型注解的第三方导入——如果存在
types-*存根包,就安装并锁定该包(存根版本独立于对应库的版本)。只有在不得已时才按模块覆盖ignore_missing_imports——绝不要使用全局标志。 - 确实存在的误报——使用限定错误代码的注释来抑制,例如
# type: ignore[arg-type](mypy)或# pyright: ignore[reportArgumentType];同时保持启用warn_unused_ignores(mypy)/reportUnnecessaryTypeIgnoreComment(pyright),以便过时的忽略规则导致构建失败。 - 绝不要通过放宽全局配置来修复错误。
8. 移交
类型检查命令现在就是约定:如果已安装 python-ci skill,它会将其转换为 CI 任务;python-precommit 会将其接入钩子。这不属于本 skill 的工作——只需确保命令在本地通过即可。
输出规范
满足以下所有条件即表示完成:
- 只有一个主要检查器,已在开发依赖中锁定,并在
pyproject.toml中配置。 - 在
src(以及tests,除非有书面理由明确排除)上运行uv run <checker> ...,退出状态码为 0。 - 已记录严格程度的决定:严格模式,或当前逐步收紧阶段,以及作为待处理事项跟踪的豁免列表。
- 对于库:包中存在
py.typed,并且scripts/check_py_typed.py通过对新构建 wheel 的检查。 - 每条抑制注释都限定了错误代码;已启用未使用忽略规则检测。
- 已在团队能找到的地方记录运行命令。
失败模式与注意事项
- mypy 显示通过 ≠ 已检查。 默认情况下,mypy 会跳过未注解函数的函数体;一次“无错误”的运行可能忽略了大部分代码库。在相信任何通过结果之前,请设置
check_untyped_defs = true(或strict)。 - 全局严格模式与逐模块降级无法配合使用。 两个工具都存在由来已久的问题(mypy #11401、pyright #601):无法干净地按模块降低单条严格规则。可行的做法:全局严格模式搭配逐模块
ignore_errors(mypy),或在standard基础配置上使用strict目录数组(pyright)。 - pyright 对旧代码的错误提示比 mypy 多得多。 它本来就会检查未注解代码,因此直接切换到
strict会一次性暴露更多错误——对于现有代码库,请从standard开始。 - wheel 中缺少 py.typed 是典型的隐蔽故障:标记文件在 git 中,但构建后端会将其从产物中丢弃,导致所有下游用户在不知情的情况下得到
Any。请务必检查构建好的 wheel,而不是源代码树(步骤 6)。 - 两个检查器,双倍抑制注释。 两个引擎的判断并不一致(kwargs 类型、
__new__推断、联合类型扩宽),因此同一行代码可能需要同时添加# type: ignore[...]和# pyright: ignore[...]。要么清楚地接受这项成本,要么只保留一个检查关卡。 - 严格模式不可移植。 mypy strict、pyright strict、basedpyright 的“recommended”预设以及 ty/Pyrefly 规则都是不同的约定——一个工具无错误,并不意味着其他工具也无错误。
- Pydantic 摩擦。 检查器无法看透
before/wrap验证器,因此使用预验证输入类型调用构造函数时会产生误报。可权衡选择的方案包括:mypy +pydantic.mypy插件、在模型模块中按规则忽略,或(前沿尝试)使用 Pyrefly 的实验性 Pydantic 集成。另外,静态检查干净的模型并不代表运行时安全:除非启用 Pydantic 自带的严格模式,否则默认会进行强制类型转换("30"会变成30)。 - ty/Pyrefly 的成熟度。 没有插件系统(Django/SQLAlchemy/Pydantic 插件堆栈无法迁移),仍处于 beta 阶段且变化频繁;从 pyright 迁移还可能失去原本能捕获的诊断。ty 的“渐进保证”(移除或放宽注解永远不会引入新错误——提高精度则可能暴露真实错误)使其非常适合作为渐进式试点工具,用于编辑器/本地,而暂不适合作为开源软件包的 CI 关卡。
- pyright 的
exclude会叠加。 自定义条目会添加到默认项(**/node_modules、**/__pycache__、**/.*)之上,且默认项优先级更高——因此无法通过在include中列出点目录来取消排除;请改为移动代码。 - Ruff 不是类型检查器。 它的 ANN 规则只检查注解风格,不会捕获任何类型错误。Lint 检查通过并不能说明这一点。
- 检查器升级会改变默认设置。 mypy 2.0(2026-05)默认启用了
--local-partial-types和--strict-bytes,更改了--allow-redefinition的语义,移除了--python-version 3.9,并新增了实验性的并行检查(--num-workers N)——常规升级可能会导致 CI 新近失败,因此步骤 3 会固定精确版本并谨慎升级。
捆绑资源
- references/checker-selection.md — 候选工具概览、对比表、选择指南,以及编辑器/CI 混合模式。
- references/strictness-ratchet.md — 分阶段启用严格模式,并提供 mypy 和 pyright 的完整配置。
- references/py-typed-distribution.md — PEP 561 分发选项、各构建后端的打包配置及验证方式。
scripts/check_py_typed.py— 检查源代码包以及构建好的 wheel/sdist 产物中是否包含py.typed;只读,失败时返回非零退出码。