Chapter 06

noqa 与规则忽略策略

规则虽然严格,但现实总有例外。学会用 noqa 精确、可审计地放行个别场景,而不是整族关掉。

6.1 四级忽略粒度

粒度写法作用域推荐场景
行级# noqa: E501当前行极个别无可奈何的情况
文件级# ruff: noqa: E501整个文件生成代码、marshmallow schema 等
路径级per-file-ignores匹配路径的所有文件tests/、__init__.py、migrations/
全局ignore = [...]整个项目确实不需要的规则

从上到下作用域越来越大,原则是 能用粒度小的就不用大的——避免"为了跳过一个 bug 把整族关掉"。

6.2 行级 noqa

import os  # noqa: F401  —— 跳过 F401 但其他规则仍生效

x = very_long_expression_cannot_be_broken_up(...)  # noqa: E501

# 跳过多条
eval(user_input)  # noqa: S307, S102

# 裸 # noqa(不带规则号)会忽略该行所有规则 —— 不推荐
dangerous()  # noqa
裸 noqa 是反模式

不带规则号的 # noqa 把这行所有规则都屏蔽了,包括未来新增的规则。Ruff 会用 PGH004(blanket-noqa)专门报告这种写法——总是写 noqa: XXX

6.3 文件级 noqa

# ruff: noqa: F401, F403
# 这是整文件的指令,放在文件顶部(通常前 5 行内)

from .models import *   # 这个文件的 F401/F403 全部放过

注意前缀是 # ruff: noqa(有冒号),与行级 # noqa 区分。

6.4 per-file-ignores:按路径放宽

比起在 20 个 __init__.py 都写 # noqa: F401,更干净的是 pyproject.toml 里一条规则:

[tool.ruff.lint.per-file-ignores]
# __init__.py 常用 from .foo import X 重新导出,允许未使用 import
"__init__.py"              = ["F401", "F403"]

# 测试文件放宽 assert、魔法常量、注解
"tests/**/*.py"            = ["S101", "ANN", "D", "PLR2004"]

# Django migrations 机器生成,不管风格
"**/migrations/*.py"       = ["E501", "N806"]

# Jupyter notebook 允许 print 与未用变量
"notebooks/**/*.ipynb"     = ["T20", "F841"]

6.5 exclude:完全跳过文件

有些目录我们希望 Ruff 根本不去读——自动生成的代码、三方 vendor、老版本备份:

[tool.ruff]
# 覆盖默认的 exclude(.git/.venv 等已默认排除)
exclude = [".git", ".venv", "dist", "build"]

# 追加不覆盖默认
extend-exclude = [
    "generated/",
    "vendor/",
    "**/_pb2.py",   # protobuf 生成文件
    "**/*_pb2_grpc.py",
]
exclude vs per-file-ignores

前者是"这些文件根本不扫",后者是"扫,但对这些文件放宽某些规则"。生成代码通常用 exclude,测试文件用 per-file-ignores(仍希望发现真 bug)。

6.6 检测无效的 noqa

时间一久,项目里会累积一堆 # noqa: XXX,但原来触发的问题已经修掉了——留着就是噪音。Ruff 有专门规则 RUF100 报告"无效 noqa":

[tool.ruff.lint]
extend-select = ["RUF100"]
$ ruff check .
demo.py:12:20: RUF100 [*] Unused `noqa` directive (unused: `F401`)

$ ruff check --fix .   # 自动清理

6.7 强制 noqa 必须带理由(可选)

团队约定每条 # noqa 后面要写原因注释。可通过 --add-noqa 配合 git review 达成:

import typing  # noqa: F401  — re-export for public API
eval(trusted_expr)  # noqa: S307  — expr 来自受控白名单,见 #1234

这不是 Ruff 内置机制,但团队可以在 code review / 预提交脚本里 grep 未带注释的 noqa 来执行这一约定。

6.8 Ruff 如何反向"加"noqa?

大仓一次性开启新规则时,会冒出成千上万条报错。与其一条条修,不如先全部标记 noqa、立即通过 CI,之后再慢慢清理:

$ ruff check --add-noqa .
# 自动在每个违规行添加对应的 # noqa: CODE 注释
# 先 CI 过了,再由后续 PR 逐个真修复

这是"先划红线、再清历史债"的常见工程策略。

6.9 小结