From 85bc0cd769cc09499aedf28055c98e9b189ebac2 Mon Sep 17 00:00:00 2001 From: wehub-skill-sync Date: Mon, 13 Jul 2026 21:36:01 +0800 Subject: [PATCH] chore: import zh skill pyrefly-type-coverage --- README.wehub.md | 9 +++ SKILL.md | 157 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 README.wehub.md create mode 100644 SKILL.md diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..d3d7c65 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,9 @@ +# WeHub 来源说明 + +- Skill 名称:`pyrefly-type-coverage` +- 中文类目:Python 静态类型注解与严格检查 +- 上游仓库:`pytorch__pytorch` +- 上游路径:`.claude/skills/pyrefly-type-coverage/SKILL.md` +- 上游链接:https://github.com/pytorch/pytorch/blob/HEAD/.claude/skills/pyrefly-type-coverage/SKILL.md +- 本仓库为 WeHub 中文 Skill 汉化包,基于 skill 市场筛选 Top200 清单整理 +- 原作者、版权和许可证信息以上游仓库为准 diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..d4f7b39 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,157 @@ +--- +name: pyrefly-type-coverage +description: 迁移文件以使用更严格的 Pyrefly 类型检查,要求所有函数、类和属性都带有注解。 +--- + +# Pyrefly 类型覆盖技能 + +## 前置条件 +- 文件必须位于包含 `pyrefly.toml` 的项目中。 +- `pyrefly`、`lintrunner` 以及项目的测试运行器必须位于 PATH 中。**如果其中任何一个缺失,请停下来询问是否需要激活 conda 环境**——不要安装或替换(根据仓库 CLAUDE.md)。 + +### 第 1 步:移除文件级别的类型检查抑制 + +从文件顶部删除以下任何内容(Pyrefly 会为了兼容 mypy 而识别 `# mypy: ignore-errors`,因此该行也必须删除): + +```python +# pyre-ignore-all-errors +# pyre-ignore-all-errors[16,21,53,56] +# @lint-ignore-every PYRELINT +# mypy: ignore-errors +``` + +### 第 2 步:向 `pyrefly.toml` 添加子配置条目 + +```toml +[[sub-config]] +matches = "path/to/directory/**" +[sub-config.errors] +implicit-import = false +implicit-any = true +bad-param-name-override = false +unannotated-return = true +unannotated-parameter = true +``` + +**重要**:在 `[sub-config.errors]` 中设置任意错误键,仅会相对于父配置覆盖该键——但启用 `unannotated-return` / `unannotated-parameter` / `implicit-any` 后,之前被文件级隐藏的错误可能会重新浮现。如果看到不相关的错误(例如 `bad-param-name-override`)大量输出,请在子配置中镜像父配置中该键的设置以将其静默。 + +### 第 3 步:运行 pyrefly + +```bash +pyrefly check +``` + +**目标:** 通过添加注解来解决所有 `unannotated-return`、`unannotated-parameter` 和 `implicit-any` 错误——参见第 4 步的阶梯。这三个目标类别始终是可解决的;**绝不**使用 `# pyrefly: ignore` 来抑制它们。唯一的例外是 `@compatibility(is_backward_compatible=True)`(第 4 步)。 + +其他类别(`bad-argument-type`、`missing-attribute` 等)是真实的类型错误。根据 pyrefly 报告的位置来处理它们: + +- **在其他文件中报告**(路径 != 目标):保持不动。不要扩大范围。如果该错误现在阻塞了目标文件,请在报告位置使用 `# pyrefly: ignore[] # TODO` 进行抑制。 +- **在目标文件中报告,但消息引用的是其他地方定义的符号**(例如,由于导入函数的注解错误导致的 `bad-return`):使用相同的 TODO 注释进行本地抑制。不要编造一个 `cast()` 来掩盖上游的缺口。 +- **在目标文件中报告,且源自本地**:修复它。 + +仅作为最后手段使用 `# pyrefly: ignore[...]`,且仅用于非目标类别。 + +### 第 4 步:添加注解 + +当从函数体无法判断正确的类型时,检查调用点。 + +#### 注解约定 + +- 使用 PEP 604 / PEP 585 语法(`int | None`、`list[str]`)——假设 Python >= 3.10。 +- 对于抽象基类(`Callable`、`Sequence`、`Generator` 等),优先使用 `collections.abc` 而非 `typing`。 +- 对于泛型辅助工具,当项目的最低 Python 版本支持时从 `typing` 导入,仅在需要使用较新特性时才从 `typing_extensions` 导入(例如,如果支持低于 3.11/3.12 的版本,则使用 `Self` 和 `override`;或者 `TypeVar` / `ParamSpec` 的 PEP 696 `default=`)。不要无差别地从 `typing_extensions` 导入。 +- 始终对 `Callable` 进行参数化——当签名确实未知时使用 `Callable[..., Any]`,绝不使用裸的 `Callable`。(关于保持签名的包装器情况,请参见下面的 ParamSpec。) +- 在 `__init__` 中赋值的类属性应添加类级别的注解,以便 pyrefly 能够识别它们。 +- 使用 `if TYPE_CHECKING:` 打破导入循环——仅注解导入放在该守卫内部,并使用 `from __future__ import annotations`(或字符串前向引用)以保持运行时导入的惰性: + ```python + from __future__ import annotations + from typing import TYPE_CHECKING + if TYPE_CHECKING: + from torch.fx import GraphModule + def transform(gm: GraphModule) -> GraphModule: ... + ``` +- **绝不抑制三个目标类别。** `unannotated-return`、`unannotated-parameter` 和 `implicit-any` 始终可以通过添加注解来解决;`# pyrefly: ignore[<其中某一个>]` 是不可接受的结果。唯一的例外是下面的向后兼容性特例。 +- **扩大类型,而非放弃。** 当正确的类型难以推断时,请遵循以下阶梯,而不是使用 ignore: + 1. 从调用点和返回路径中可观察到的最具体的具体类型。 + 2. 联合类型(`X | Y`)、`Sequence[X]` 风格的抽象类型,或者对于真正泛型函数(恒等传递、容器辅助函数)使用有界的 `TypeVar`。 + 3. `object`——仍然能通过类型检查的最严格的兜底类型。强制调用者在使用前进行窄化,例如 `def serialize(value: object) -> str:`。视觉上类似于 `Any`,但更严格——pyrefly 会在没有 `isinstance` 的情况下拒绝 `value.foo()`。 + 4. `Any`——最后一阶。始终优于在目标类别上使用 `# pyrefly: ignore`,但仅在前 1–3 阶失败之后使用。必须能够说明为什么每个更早的阶梯都不适用(例如,「联合类型超过 8 种」、「没有可观察到的公共边界」、「调用者确实从不窄化」)。 +- 在决定某个参数必须是 `Any` 之前,至少阅读三个调用点——不要在第一次尝试时就模式匹配「看起来是动态的」。 +- 小范围的 `# pyrefly: ignore[...]`(用于非目标类别)保留给 pyrefly *确实错误* 的情况——动态元编程、第三方存根缺口: + ```python + # pyrefly: ignore[attr-defined] + result = getattr(obj, dynamic_name)() + ``` + +#### 向后兼容性(从不抑制规则的唯一例外) + +**关键**:使用 `@compatibility(is_backward_compatible=True)` 装饰的函数**绝不能**更改其签名。向后兼容性测试(`test_function_back_compat`)会将字符串化的 `inspect.signature` 与黄金文件进行比较——添加注解(即使是 `-> None`)也会改变该字符串,导致测试失败。请改用 pyrefly ignore 注释: + +```python +@compatibility(is_backward_compatible=True) +def my_function( # pyrefly: ignore[unannotated-return] + self, + arg1, # 这里也不能添加类型 +): + ... +``` + +`# pyrefly: ignore` 注释必须在 `def` 行(pyrefly 报告错误的位置),而不是在结尾的 `)` 上。 + +**保持签名的包装器的 ParamSpec**(装饰器、`functools.wraps` 风格的辅助函数)。使用 `Callable[P, R]`,使包装函数的签名能够传递给调用者——`Callable[..., Any]` 会丢失签名。如果包装器确实接受任意可调用对象,则跳过 ParamSpec。当包装器前置或追加参数时,与 `Concatenate[X, P]` 配合使用。 + +```python +from collections.abc import Callable +from typing import ParamSpec, TypeVar + +P = ParamSpec("P") +R = TypeVar("R") + +def log_calls(fn: Callable[P, R]) -> Callable[P, R]: + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + return fn(*args, **kwargs) + return wrapper +``` + +### 第 5 步:迭代 + +重新运行 `pyrefly check`。新的注解通常会暴露出 `bad-return` 错误,表明函数实际返回了不兼容的类型——修复这些错误。重复直到没有错误。 + +### 第 6 步:代码检查 + +在移交之前必须执行——注解经常改变导入顺序和行长度: + +```bash +lintrunner -a +``` + +手动解决 `lintrunner` 无法自动修复的问题。 + +### 第 7 步:测试 + +**当某些测试失败时的优先级**:测试通过 > pyrefly 无错误 > 注解严格度。如果新添加的注解导致测试失败,在回退文件之前,按严格度阶梯下降一级(例如,从具体类型降到 `object`,或者移除导致下游 `isinstance` 检查失败的 `Any` 放宽)。 + +1. **向后兼容性检查。** 仅在以下情况下运行: + `grep -l '@compatibility(is_backward_compatible=True)' ` 返回该文件——该装饰器才是黄金文件的真正前置条件。更宽泛的「导入了 `torch.fx`」启发式规则会命中 `torch/` 的一半内容。 + ```bash + python -m pytest test/test_fx.py::TestFXAPIBackwardCompatibility -x -v + ``` + +2. **被修改模块的单元测试。** 在得出没有覆盖测试的结论之前,双向搜索: + ```bash + # torch/foo/bar.py 通常由 test/test_foo.py 或 test/test_bar.py 覆盖 + ls test/ | grep -i + # 或者通过导入来查找 + grep -rl "from torch.foo.bar import\|import torch.foo.bar" test/ + ``` + + 如果两者都未找到,请告知用户——不要默默地跳过。类型变更可能引入真实的运行时退化(`Optional[X]` 与 `X`、`Sequence` 与 `list` 在调用 `.append` 时等)。 + +## 备注 + +- **类体中的前向引用**在没有 `from __future__ import annotations` 时仍需要字符串引号: + ```python + class MyClass: + def __new__(cls) -> "MyClass": ... + ``` +- **提交**:除非用户明确要求,否则不要提交(根据仓库 CLAUDE.md)。当文件干净时,停下来展示差异以供审查。