chore: import zh skill pyrefly-type-coverage
This commit is contained in:
@@ -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 清单整理
|
||||
- 原作者、版权和许可证信息以上游仓库为准
|
||||
@@ -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)。当文件干净时,停下来展示差异以供审查。
|
||||
Reference in New Issue
Block a user