首页
/ `string-or-bytes-too-long` (`PYI053`)

`string-or-bytes-too-long` (`PYI053`)

2026-09-07 18:12:34作者:苗圣禹Peter
[lint]
select = ["PYI053"]

Long name in __all__

Strings in __all__ correspond to exported names and should be exempt from the rule.

__all__ = [
    "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeef",
]

把它拆解开来,每一部分都有明确语义:

| 文档元素 | mdtest 语义 |
| --- | --- |
| 一级标题 ``# `string-or-bytes-too-long` (`PYI053`)`` | 测试套件(测试用例集)的名称,同时也是被测规则的标识 |
| `toml` 围栏代码块 | **配置块**:被反序列化为 Ruff 的 `Options`,驱动本条测试的运行配置,等价于上面的 `[lint] select` |
| `## Long name in `__all__`` | 一个小节标题,也是一个独立**测试用例**的名字;测试名会由各级小节标题拼接而成 |
| `pyi` 围栏代码块 | 被检代码:会被写入内存中的 `mdtest_snippet.pyi` 文件后交给 linter 执行 |
| 无任何 `# error:` 断言注释 | 意味着**期望该片段不产生任何诊断** |

用例标题 “Long name in `__all__`” 直白地描述了本条测试的意图:`__all__` 中的字符串对应的是模块导出的名称,**应被本规则豁免**。代码块里放置的字符串足足有 51 个字符(10 个 `a` + 10 个 `b` + 10 个 `c` + 10 个 `d` + 10 个 `e` + 1 个 `f`),远超 50 字符阈值,却没有附带任何 `# error` 期望注释,因此该测试的断言就是:**即使超过 50 个字符,`__all__` 里的字符串也不得报出 `PYI053`**。

### mdtest 测试是怎么跑起来的

这些 `resources/mdtest/**/*.md` 文件由 [crates/ruff_mdtest/tests/mdtest.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_mdtest/tests/mdtest.rs?utm_source=gitcode_repo_files) 通过 `datatest_stable` 以 `root = "../ruff_linter/resources/mdtest", pattern = r"\.md$"` 的方式全部收集为集成测试。每个 Markdown 文件被解析成一个测试套件,内部每个含可检代码块的 `#`/`##` 小节则成为一个测试用例。

[parser.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/mdtest/src/parser.rs?utm_source=gitcode_repo_files) 负责把 Markdown 解析为结构化的 `MarkdownTestSuite`:

- 匿名的 `toml` 围栏块被视为该小节的配置([crates/mdtest/src/parser.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/mdtest/src/parser.rs?utm_source=gitcode_repo_files#L839-L841) 与 `process_config_block`),解析失败会直接报错;同一小节内不允许出现多个 TOML 配置块;
- 语言为 `pyi` 的围栏块会自动生成文件名 `mdtest_snippet.pyi`([crates/mdtest/src/parser.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/mdtest/src/parser.rs?utm_source=gitcode_repo_files#L351-L356)),`py`/`python` 对应 `mdtest_snippet.py`,`ipynb` 对应笔记本文件;也支持显式命名文件(如 `` `module.py`: ``)以及 `toml`、`text` 等;
- 同一小节内多个无显式命名的代码块会被**合并拼接**成同一个文件,用于模拟“片段间共享上下文”的场景;
- 代码块必须换行书写且前面要有至少一个空行,格式不规范时解析器会拒绝。

真正执行时,[crates/ruff_mdtest/src/lib.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_mdtest/src/lib.rs?utm_source=gitcode_repo_files) 会:

1. 在内存文件系统中把每个围栏块写入 `/src` 项目根下的对应文件(`run_test` 中的 `db.write_file`);
2. 用该小节 TOML 配置构造出真实的 Ruff 设置(`Configuration::from_options(...)`);
3. 通过 [ruff_linter 的测试入口](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_linter/src/test.rs?utm_source=gitcode_repo_files)(`test_contents`)对 `.pyi` 源码执行 lint,得到诊断;
4. 交由 [crates/mdtest/src/matcher.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/mdtest/src/matcher.rs?utm_source=gitcode_repo_files) 的 `match_file` 把每行诊断与该行的内联断言注释逐一比对。**没有写断言注释的行若产生任何诊断,就会被当作 “unexpected error” 导致测试失败**——这正是“`__all__` 用例如不豁免就会失败”的机制保证。

运行整个 mdtest 测试套件只需:

```bash
cargo test -p ruff_mdtest
```

由于小节标题就是测试名,也可以只跑与 `__all__` 豁免相关的这条用例(框架读取环境变量 `MDTEST_TEST_FILTER` 进行名字过滤,见 [crates/mdtest/src/lib.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/mdtest/src/lib.rs?utm_source=gitcode_repo_files#L20-L26)):

```bash
MDTEST_TEST_FILTER="__all__" cargo test -p ruff_mdtest
```

## 四、`__all__` 豁免的源码机制

回到规则实现 [string_or_bytes_too_long.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs?utm_source=gitcode_repo_files#L55-L96),`PYI053` 的检查函数在报告诊断之前会依次做多次“放行”判断,其中与本文档测试用例直接相关的就是 `__all__` 检查:

```rust
if checker.in_dunder_all_assignment(parent) {
    return;
}
```

`parent` 是当前字符串所在的语句,`in_dunder_all_assignment` 判断该语句是否为对 `__all__`(模块级魔术变量)的赋值。如果命中,函数直接返回、不产生诊断。规则文档注释也写明:**规则不适用于 `__all__` 中的超长条目,因为可假定那些导出名不在存根作者的可控范围内**(“The rule does not apply to long entries in `__all__`, which are assumed to be outside the stub author's control.”)。

也就是说,即使某个模块实际导出的名字很长(例如为了兼容旧版本而保留的超长别名),存根作者也无法更改它,此时要求用 `...` 替换会破坏存根的语义正确性,因此必须放行。mdtest 里的那段 51 字符示例,正是对这个分支行为的回归验证。

## 五、更多豁免与边界行为:源码级纵览

除了 `__all__`,规则实现里还藏着一整套边界处理,理解它们有助于你判断手头的 `.pyi` 会不会被 `PYI053` 误报。

### 仅作用于存根(`.pyi`)文件

`PYI053` 的规则入口不是独立的 AST 遍历,而是挂在“字符串类表达式”的公共分析点上。在 [crates/ruff_linter/src/checkers/ast/analyze/string_like.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_linter/src/checkers/ast/analyze/string_like.rs?utm_source=gitcode_repo_files#L21-L25) 中可以看到显式的文件类型门控:

```rust
if checker.source_type.is_stub() {
    if checker.is_rule_enabled(Rule::StringOrBytesTooLong) {
        flake8_pyi::rules::string_or_bytes_too_long(checker, string_like);
    }
}
```

即只有当当前文件是 stub(`.pyi`)时才触发。该分析函数由 AST 检查器在访问每个表达式节点时调用,字符串字面量、字节字面量、f-string 与模板字符串(TString)都会走到这里(见 [crates/ruff_linter/src/checkers/ast/mod.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_linter/src/checkers/ast/mod.rs?utm_source=gitcode_repo_files#L2283-L2291))。规则在规则表中的注册位置见 [crates/ruff_linter/src/codes.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_linter/src/codes.rs?utm_source=gitcode_repo_files#L978)。

### docstring 豁免

如果超长字符串本身就是 docstring(当前语句是文档字符串语句),则跳过:

```rust
if is_docstring_stmt(parent) {
    return;
}
```

存根中的文档说明文字不计入此规则。

### 类型定义上下文豁免

在类型定义与延迟求值的类型定义(如尚未展开的类型别名、`Annotated`/字符串化注解等被 deferred 处理的场景)内部,规则同样放行:

```rust
if semantic.in_type_definition() | semantic.in_deferred_type_definition() {
    return;
}
```

### `warnings.deprecated` / `typing_extensions.deprecated` 消息豁免

用于标记弃用的超长说明消息是有价值的运行时/IDE 信息,不应被替换成 `...`。规则通过 `is_warnings_dot_deprecated` 判断当前字符串的父表达式是否为对 `warnings.deprecated(...)` 或 `typing_extensions.deprecated(...)` 的调用([crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs?utm_source=gitcode_repo_files#L120-L136)),命中则跳过。

### 不同字面量类型的长度口径

阈值 50 的判断依据字面量类型而不同([crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs?utm_source=gitcode_repo_files#L77-L89)):

| 字面量类型 | 长度统计方式 |
| --- | --- |
| 普通字符串 `str` | `value.chars().count()`:按 **Unicode 码点数量**统计,而不是按字节数或显示列宽 |
| 字节字面量 `bytes` | `value.len()`:按**字节数**统计 |
| f-string | 由 `count_f_string_chars` 统计:累加所有字面量分段的码点数,并把每个 `{插值表达式}` 按其在源码中的 range 长度计入 |
| 模板字符串 TString | 直接跳过(源码中留有 TODO,尚未决定插值部分的精确计数口径) |

f-string 的计数函数还处理了隐式拼接的多个 f-string 分段(`f_string.value.iter()`),并对字符串分段取 `chars().count()`、对插值段取 `expr.range().len()`,因此**带插值的 f-string 是“近似统计”**——插值本身的源码宽度(含 `{}` 与内部空白)也会被计入总长度。这一近似口径在源码注释中标注了待完善的 TODO。

## 六、自动修复:整段替换为 `...`

一旦确认违规,规则会报告一个**安全修复(safe fix)**:把整个超长字面量(含其引号/前缀/隐式拼接的完整 range)替换成 `...`。

```rust
let mut diagnostic = checker.report_diagnostic(StringOrBytesTooLong, string.range());
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
    "...".to_string(),
    string.range(),
)));
```

因此像 `def f2(x: str = "51 character ...") -> None: ...` 这样的代码执行 `ruff check --fix`(或 IDE 的快速修复)后,会被改写为:

```pyi
def f2(x: str = ...) -> None: ...
```

## 七、回归快照与测试夹具

规则的常规测试还通过快照测试覆盖了大量边界,测试用例注册位于 [crates/ruff_linter/src/rules/flake8_pyi/mod.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_linter/src/rules/flake8_pyi/mod.rs?utm_source=gitcode_repo_files#L85-L86),同时用 `.py` 与 `.pyi` 两种夹具验证“普通 Python 不受影响、存根才受检”。生成快照 [ruff_linter__rules__flake8_pyi__tests__string-or-bytes-too-long_PYI053.pyi.snap](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__string-or-bytes-too-long_PYI053.pyi.snap?utm_source=gitcode_repo_files) 直接印证了上文所有边界:

- 恰好 50 字符的 `str`/`bytes`/f-string 默认值均标注 `# OK`;
- 51 字符的默认值报出 `PYI053`,并给出 `help: Replace with ...` 与逐行 diff(`x: str = "..."` 改为 `x: str = ...`);
- 50 个 ASCII 字符后再加一个 `\U0001f600`(emoji,UTF-8 下占 4 字节)依然触发——证明字符串按码点而非字节计数;
- 字节字面量含 `\xff` 转义时按字节数累计,超过 50 即触发;
- 带 `{foo}` 插值、总长超限的 f-string 同样被报;
- 装饰器 `@not_warnings_dot_deprecated("超长消息")` 会被报——它刻意不是 `warnings.deprecated`/`typing_extensions.deprecated`,用于证明豁免只针对真正的弃用标记调用。

## 八、内联断言与 `snapshot` 块:mdtest 的两种写法

关联文档使用的是“零断言”写法(无诊断即通过)。除此之外,mdtest 还支持在 Python 代码行尾写内联断言注释,由 [crates/mdtest/src/assertion.rs](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/mdtest/src/assertion.rs?utm_source=gitcode_repo_files) 解析,格式包括:

- `# error` / `# error: [PYI053]`:期望本行产生(该规则的)一个诊断,可带 1-based 列号与消息片段;
- `# snapshot: [PYI053]`:期望本行产生该规则的诊断并纳入快照校验;
- `# revealed: <类型>`:用于类型检查(ty)场景的 `reveal_type` 断言。

同目录下的姊妹测试 [crates/ruff_linter/resources/mdtest/flake8-pyi/redundant-numeric-union.md](https://gitcode.com/GitHub_Trending/ru/ruff/blob/0044db22fa125d977c3ea909a70e8dcadc35b78b/crates/ruff_linter/resources/mdtest/flake8-pyi/redundant-numeric-union.md?utm_source=gitcode_repo_files) 就是“断言式”写法的范例:它在 Python 片段行尾标注 `# error: [PYI041]`,并用紧随其后的 `
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388