chore: import zh skill pyrefly-type-coverage

This commit is contained in:
wehub-skill-sync
2026-07-13 21:36:01 +08:00
commit 85bc0cd769
2 changed files with 166 additions and 0 deletions
+9
View File
@@ -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 清单整理
- 原作者、版权和许可证信息以上游仓库为准
+157
View File
@@ -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 <FILENAME>
```
**目标:** 通过添加注解来解决所有 `unannotated-return``unannotated-parameter``implicit-any` 错误——参见第 4 步的阶梯。这三个目标类别始终是可解决的;**绝不**使用 `# pyrefly: ignore` 来抑制它们。唯一的例外是 `@compatibility(is_backward_compatible=True)`(第 4 步)。
其他类别(`bad-argument-type``missing-attribute` 等)是真实的类型错误。根据 pyrefly 报告的位置来处理它们:
- **在其他文件中报告**(路径 != 目标):保持不动。不要扩大范围。如果该错误现在阻塞了目标文件,请在报告位置使用 `# pyrefly: ignore[<category>] # 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 <files...>
```
手动解决 `lintrunner` 无法自动修复的问题。
### 第 7 步:测试
**当某些测试失败时的优先级**:测试通过 > pyrefly 无错误 > 注解严格度。如果新添加的注解导致测试失败,在回退文件之前,按严格度阶梯下降一级(例如,从具体类型降到 `object`,或者移除导致下游 `isinstance` 检查失败的 `Any` 放宽)。
1. **向后兼容性检查。** 仅在以下情况下运行:
`grep -l '@compatibility(is_backward_compatible=True)' <target>` 返回该文件——该装饰器才是黄金文件的真正前置条件。更宽泛的「导入了 `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 <module-name>
# 或者通过导入来查找
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)。当文件干净时,停下来展示差异以供审查。