Ruff 0.11.x 版本演进全解析:Python 版本推断新机制、PGH004 稳定化与 Compile-time Syntax Errors 体系
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
本文围绕 Ruff 官方变更记录 changelogs/0.11.x.md 展开,完整覆盖 0.11.0 至 0.11.13 共 14 个版本的核心变更。读完本文,你将掌握 0.11.0 中requires-python推断这一破坏性变更的确切语义与源码实现、PGH004稳定化后的检测范围,以及贯穿整个 0.11.x 系列的 compile-time syntax errors(编译期语法错误)检测体系是如何逐版本铺开的,并了解--exit-non-zero-on-format等 CLI 新选项的实际行为。
0.11.0 的由来:一次发布流程修正
0.11.0 并不是一个常规的迭代版本,而是对 0.10.0 的补发(follow-up release)。变更记录开篇明确说明:由于发布流程中的一个失误,requires-python推断相关的变更没有包含进 0.10.0,因此 Ruff 0.11.0 补上了这一变更,同时完成了PGH004preview 行为的稳定化。也就是说,升级 0.11.0 时实际引入的“新行为”有两块:Python 版本推断机制的变更(破坏性变更)与blanket-noqa检测范围的扩大。
破坏性变更:target-version缺失时的版本推断
在此之前,指定 Python 版本只有两条途径:
- 在
ruff.toml(或pyproject.toml的[tool.ruff]段)中显式配置target-version; - 在带有
[tool.ruff]段的pyproject.toml中依赖project.requires-python字段。
问题出在 Ruff 的配置发现机制上:一个不含[tool.ruff]段的pyproject.toml会被整个忽略,其中的requires-python也随之被忽略,Ruff 会回退到默认 Python 版本(记录写作时为 3.9)——当你明明声明了目标版本时,这个行为出人意料。
0.11.0 更新后的配置发现规则如下(变更记录原文列出三条,当前 docs/configuration.md 中“Inferring the Python version”一节的完整表述为四条):
- 如果直接传入了配置文件,Ruff 不会尝试推断缺失的
target-version; - 如果在文件系统层级中发现配置文件(如
ruff.toml没有target-version),Ruff 会检查同一目录下的pyproject.toml,并尊重其requires-python字段——即使该文件不含[tool.ruff]段; - 如果使用用户级配置(user-level configuration),则取父目录中最近的
pyproject.toml的requires-python字段优先; - 如果被检查文件所在目录没有任何配置文件(
ruff.toml或带[tool.ruff]段的pyproject.toml),Ruff 会向上查找最近的pyproject.toml并使用其requires-python。
源码实现:apply_fallbacks与回退版本推导
从源码结构看,这套逻辑的落点在ruff_workspacecrate:
- configuration.rs 中的
apply_fallbacks方法:当配置来源为ConfigurationOrigin::Ancestor(即从父目录层级发现)且target_version为空时,调用pyproject::find_fallback_target_version(dir)推导回退版本; - pyproject.rs 中的
find_fallback_target_version沿路径祖先目录逐级查找,找到第一个含requires-python的pyproject.toml即返回; - 关键的版本提取逻辑在 pyproject.rs 的
get_minimum_supported_version:它从VersionSpecifiers中只筛选==、===、~=、>=、>等下界类操作符,取各操作数版本截断到 major.minor 后的最小值,再映射到PythonVersion枚举。例如requires-python = ">=3.13"会推导出py313。
需要注意的细节:load_options(pyproject.rs)在解析pyproject.toml时,如果其中已显式给出target_version,则显式配置优先,不会再去读requires-python。也就是说,target-version依旧是“细粒度控制”的第一选择,推断只作为兜底。
PGH004(blanket-noqa)稳定化
blanket-noqa规则(PGH004)在 0.11.0 稳定化,行为变化是:同时检测文件级的 blanket noqa 注释,而不仅仅是行级注释。即# noqa整行/整文件地关闭所有规则的行为现在也会被告警。配合 0.11.5 中RUF100对“带具体规则码的未使用文件级noqa指令”的检测修复,0.11.x 系列对 noqa 卫生管理的整体覆盖明显增强。
贯穿系列的 Preview 主线:compile-time syntax errors
0.11.x 系列最具系统性的一条线索是[syntax-errors]类别的 preview 功能——把 CPython 在编译期才会报错的语法问题,前移到 lint 阶段检测。按版本梳理其演进:
- 0.11.1:一批针对“低版本 Python 使用高版本语法”的检测先行落地,包括 PEP 701 f-string 在 Python 3.12 之前、括号化上下文管理器(PEP 634/614 相关)在 3.9 之前、星号注解(PEP 646)在 3.11 之前、
for语句迭代子句中的元组解包在 3.9 之前等;同时改进了 pre-PEP-614 装饰器语法错误的消息与范围(range); - 0.11.2:修复 3.11 之前可变参数(variadic)注解上的误报;
- 0.11.3:正式宣布Start detecting compile-time syntax errors(开始检测编译期语法错误),并补充 match 模式相关问题(映射模式重复键、类模式重复属性、
case模式中多重赋值等)、__debug__赋值/删除检测; - 0.11.4:补充注解(annotations)中的非法语法检测、
match模式中的重复键/属性;同期还实现了RUF102(invalid-rule-code); - 0.11.5:将注解检查扩展到
await表达式与带注解赋值,新增同步推导式中出现异步推导式的检测; - 0.11.8:新增模块级
nonlocal声明、单星号赋值x = *y检测,并将“未加括号的 except 元组”语法错误限定为 3.14 之前的行为; - 0.11.9:两项关键调整——版本相关的语法错误默认按最新受支持 Python 版本判定,以及为 Python 3.14 实现 deferred annotations;
- 0.11.13(其他变更):parser 与 formatter 双双支持 Python 3.14 的 t-strings(模板字符串)。
这条线索的意义在于:Ruff 的 Rust 解析器在解析成功之后,还维护了一套semantic_errors检查层(可参见 ruff_python_parser 与 error.rs 的目录结构),0.11.x 系列把它逐步暴露为可启用的 preview 诊断,让“这份代码在目标 Python 版本下根本编译不过”的问题在 lint 阶段即可被捕获。由于均处于 preview 状态,启用方式是在配置或 CLI 中开启preview模式;正式稳定化前的行为仍可能调整。
值得逐条关注的规则与修复变更
Airflow 规则(AIR3xx)的大规模演进
flake8-airflow插件是 0.11.x 中改动最密集的插件,核心是 Airflow 3 的模块迁移检测(AIR301/AIR302等):从 0.11.1 起陆续补充chain、chain_linear、cross_downstream检测,0.11.3 引入AIR312拆分、规则编号多次迁移(AIR301→AIR002、AIR302→AIR301、AIR303→AIR302等,使用这些编码时注意版本差异),0.11.10–0.11.13 则持续补全 autofix(含针对 Airflow 3 中重命名场景的不安全修复)并扩展到单个符号级别的模块路径检查。如果你的项目依赖 Airflow 且使用这些规则,升级时应留意规则码映射变化。
CLI:--exit-non-zero-on-format(0.11.1)
这是一个 CI 场景下很实用的新选项:只要格式化实际修改了任何文件,即使全部文件都格式化成功,ruff format也返回非零退出码。实现位于 args.rs,定义为FormatCommand的布尔参数,并带有历史别名--exit-non-zero-on-fix。其典型用法是让 CI 以“检查+失败”的方式守护格式一致性,而不是静默修改工作区。
配置与 CLI 的其他新增
- 0.11.3:stdin 传入的
pyproject.toml现在能被正确解析检查(此前作为普通文本处理会导致误判);flake8-import-conventions的默认别名新增numpy.typing as npt; - 0.11.8:新增禁用
typing_extensions相关导入建议的选项(对应 configuration.rs 中LintConfiguration的typing_extensions字段,0.11.9 还修复了该设置缺少combine调用的 bug);配置选项中新增 Python 3.14(target-version可用py314); - 0.11.9:
ruff analyze graph允许传入虚拟环境。
规则行为变更与自动修复安全性收紧
0.11.x 系列体现了一个清晰的工程取向——把更多自动修复标记为“不安全(unsafe)”以避免破坏性变更,例如:
- 0.11.7:
FURB161(refurb)修复除整数字面量与布尔量外一律不安全;PLR1730在删除注释时不安全; - 0.11.10:
PIE804在字典含注释时修复标记为不安全; - 0.11.12/0.11.13:
UP010、UP004、UP050在删除注释时标记为不安全; - 同期也有“放宽”方向:0.11.7 中
E712(冗余布尔比较)获得自动修复,PERF401允许用推导式替换列表函数调用。
若你的 CI 使用--fix而非--unsafe-fixes,这些安全性调整直接影响修复覆盖面,升级后值得对 diff 做一次抽查。
代表性 bug 修复
变更记录中篇幅最大的部分是 bug fixes,其中几类修复模式值得关注:
- 文件描述符(fd)参数导致的误报:0.11.8–0.11.10 集中修复了
flake8-use-pathlib一族规则(PTH104/PTH116/PTH123/PTH208等)在函数参数是文件描述符时仍建议Path方法的误报,0.11.10 进一步推广为“对所有带dir_fd参数的os.*函数抑制诊断”; - 自动修复产生非法代码:如
SIM905的rsplit修复会产生反转的列表字面量(0.11.10 修复)、UP018修复丢失空格或括号(0.11.7/0.11.9 修复)、FURB129修复生成非法语法(0.11.12 修复); - 稳定性:0.11.1 中 Server 端在有版本特定语法错误时允许
FixAll操作;0.11.3 中“从已删除目录运行 Ruff 时panic!”改为正常报错。
升级建议
- 如果从 0.10.x 或更早版本升级到 0.11.x,首要关注的是未配置
target-version的项目:新推断机制可能改变 Ruff 实际使用的目标版本(尤其是目录中存在pyproject.toml但无[tool.ruff]段、且声明了requires-python的场景),从而改变部分版本相关规则(如UP系列 pyupgrade 规则)的触发行为。可用ruff check --show-settings查看解析后的target-version验证; - 使用 Airflow 相关规则码的项目,需对照 0.11.3 起的规则码迁移记录核对配置;
- 依赖
ruff format退出码的 CI 脚本,可考虑改用--exit-non-zero-on-format获得“有文件被改动即失败”的语义; - preview 相关的 syntax-errors 检测在整个 0.11.x 系列中持续变动且默认不启用,建议在稳定化之前不要在非 preview 流程中依赖其输出。
本系列变更记录的完整明细(含全部 PR 编号)见 changelogs/0.11.x.md,版本推断的现行文档表述见 docs/configuration.md。
【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考