打算在项目里把Ruff的规则好好筛一遍的人,应该都遇到过那个困惑:ruff list命令把六百多条规则一次性铺开,光看都看不过来,更别说怎么从中挑出自己想要的。我也是在调一个老项目的检查策略时,整天和ruff list --select N这个组合命令打交道,折腾了几天才把筛选逻辑彻底弄明白。我把自己的实操过程整理成文,希望能帮你省掉踩坑的时间。这篇文章会从规则代码的结构讲起,逐步拆解--select N的匹配逻辑,再用几个真实场景演示完整筛选流程,最后一并列出我实际遇到过的坑。
Ruff是Python社区里目前相当主流的静态检查工具,底层用Rust实现,跑得飞快,而且内置了几百条规则,覆盖代码风格、错误隐患、导入规范、命名规范、安全检测等方向。但规则多反而是把双刃剑:不做筛选就把所有规则打开,项目里会瞬间充满噪音,真正重要的错误反而被淹没。所以“筛选规则”本身,就是一个值得花时间研究的正规操作。
1. 规则筛选前必须搞懂的事:规则代码长什么样
1.1ruff list输出的三列信息
ruff list命令的作用非常直接:把你当前安装的Ruff支持的所有规则一口气列出来。输出默认是文本表格,每一行一条规则,核心是三列:规则代码、规则名、所属大类。大致感觉是这个样子:
E501 line-too-long pycodestyle-errors F401 unused-import pyflakes ...第一条E501就是大家很熟悉的行长度检查规则,line-too-long是这个规则的人类可读名称,pycodestyle-errors表示这条规则源自pycodestyle的error类。这三列信息其实已经告诉你筛选的时候该怎么下手了:你既能看到代码前缀,又能看到含义,还能知道它归属哪个检查体系。
第一次执行ruff list的时候不要慌,你看到的不是整个列表有什么问题,而是Ruff把“规则菜单”完整端到了你面前。六百多条规则虽然多,但它们不是平铺的,而是按前缀进行了天然分组。你在终端里可以配合less逐页翻看,也可以直接接管道用grep按关键词筛选,比如想确认所有和“unused”(未使用)相关的规则,执行ruff list | grep -i unused就够了。
1.2 规则代码的字母前缀就是“筛选抓手”
Ruff的每条规则都有一个唯一代码,形式是“字母前缀+数字编号”,比如E501、F401、B006。字母前缀代表这套规则的来源或主题领域,数字编号是具体规则的序号。这里的字母前缀就是--select N筛选的核心抓手。
我整理了一份实用前缀对照表,都是日常最常用的几类:
| 前缀 | 来源或方向 | 示例规则 | 简单说明 |
|---|---|---|---|
| E | pycodestyle - error | E501 | 行长度、空白、空行等格式错误类 |
| W | pycodestyle - warning | W605 | 格式边界情况的警告类 |
| F | Pyflakes | F401 | 未使用导入、未定义名称等逻辑问题 |
| B | flake8-bugbear | B006 | bug隐患,比如可变对象做函数默认参数 |
| I | isort分类 | I001 | import导入排序规范 |
| N | pep8-naming | N801 | 命名规范,比如类名、函数名大小写 |
| S | flake8-bandit | S101 | 安全相关,比如检测assert用于除调试外的场景 |
| C | mccabe 复杂度 | C901 | 函数复杂度过高检测 |
| UP | pyupgrade | UP032 | 老语法升级建议,比如提示改f-string |
看到这个表你会发现一个规律:--select E意味着把pycodestyle的error类规则全选上,--select F就是把Pyflakes的规则全选上,--select N自然就是命名规范的整套规则。这也是--select N里那个N在日常使用中最常见的含义——它表示你要筛选的规则前缀,比如“我想筛选命名相关的N系列规则”。
1.3 为什么不能一把梭全开
有些刚上手Ruff的人会觉得,规则全开不是最保险吗?实际体验过就知道完全不是那么回事。规则一全开,几百条提示里面有大量的风格偏好类警告,这些警告不一定是错误。比如某种内置函数被覆盖了、某个命名风格建议改一下,这些建议可能是合理的,但在某个具体项目里噪声大于价值。
我自己维护的老项目里就有个经典案例:用到了大量动态生成类名的元编程代码,如果打开整组N系列命名规则,几百个类名报错,但代码本身在业务上下文里完全成立。这时候需要的不是“规则全开”,而是“先把N系列整体看一遍,再决定开哪几条、放哪几条”。筛选规则不是逃避检查,而是让检查更贴合你的项目现状。
2.--select N的匹配逻辑:这个N不是数字那么简单
2.1 四种写法对应四种粒度
--select N这个参数看起来简洁,实际能接受的值有四种不同粒度:
- 完整规则代码:
--select F401,只启用未使用导入检查这一条规则,粒度最细。 - 两位前缀:
--select E5,启用所有以E5开头的规则,一类规则里的一个子分组。 - 单字母前缀:
--select E,启用整个pycodestyle error类,粒度最粗。 - 多个值组合:
--select E,F,I,把几个不同前缀的规则集合并启用,这是日常用得最多的写法。
注意,--select后面跟的N并不是一个数字编号,而是一套“规则代码匹配模式”。Ruff拿到你的参数后,会按规则代码前缀去匹配,看哪些规则的代码以这个前缀开头。这个设计思路和flake8一脉相承,但Ruff实现得更干净。比如你写--select F4,实际匹配的就是F401、F403、F405这类以F4开头的规则,而不是“第4个F规则”。
2.2 精确匹配和前缀匹配的边界
有件事值得专门说一下:即使你可以用--select E把整个E系列打开,Ruff真正启用规则时还会考虑规则的具体前缀是否在“允许列表”里。简单理解就是,ruff list --select E是“列出E系列全部规则”,而ruff check --select E .是“检查时启用E系列全部规则”,两者在“筛选”这个动作上是一致的,区别只在“看”和“用”。
在命令行上,ruff list --select E501会精确列出一条规则,加一个--select E则会列出所有E开头的规则。你可以先通过list界面反复试探,确认某种写法能筛出什么结果,再放心地组合到你最终需要的select配置里。这种“先list后check”的思路,是筛选规则最稳的路径。
2.3 select和ignore是怎么互相配合的
Ruff里还有另一个参数叫--ignore,和--select是天生的一对。--select决定“从规则库里选哪些”,--ignore决定“已经选中的集合里再剔掉哪些”。优先级顺序是先选出集合,再从集合里排除。
举个例子,我想启用B系列全部bug隐患检查,但项目里有一个旧的公共函数大量使用了可变默认参数,这条B006一时半会改不完,那我就可以写--select B --ignore B006。这条命令先选中B系列全部规则,然后明确忽略B006这一条,既保持了对该隐患的感知能力,又给了存量代码改造的缓冲期。
这个组合逻辑在list命令里同样生效:ruff list --select B --ignore B006列出的结果里就没有B006了。我经常用这个命令在改动配置前先“预演”一遍,确认最终生效的规则集合长什么样,避免直接改动配置后发现和预期不符。
2.4 配置文件里对应的写法
--select N的命令行参数,写进配置文件时就是静态配置里的select列表。以最常见的pyproject.toml为例,配置长这样:
[tool.ruff.lint] select = ["E", "F", "B", "I", "N"] ignore = ["B006"]注意新版Ruff把lint相关配置放在[tool.ruff.lint]下面,而不是老的[tool.ruff],这是因为Ruff后来加入了formatter功能,需要把lint和format两组配置区分开。如果你把select直接写在[tool.ruff]下,新版Ruff会提示配置位置不对,虽然还能运行,但格式上已经算过时了。
命令行参数和配置文件之间是覆盖关系:命令行临时指定的select优先级更高,会覆盖配置文件里的select。这一点在排查问题的时候特别重要,见后文。
3. 实操过程:从需求到命令的完整筛选流程
3.1 四步法:从规则库到最终配置
我在实际操作中总结出了一个四步法,用来应对“想启用某种检查但不知道规则代码是什么”的情况:
- 先用
ruff list --select 某个前缀或ruff list | grep 关键词定位候选规则。 - 用list的输出确认规则代码和含义,挑出真正需要的规则。
- 在命令行用
ruff check --select 候选规则 .临时验证效果,看会不会和项目现状冲突。 - 确认无误后把代码写进
pyproject.toml的select列表,形成持久配置。
这套流程的核心思想是“先看菜单,再点菜,最后下单”。Ruff的命令行工具非常灵活,给了充分的试错空间,完全没必要一上来就动配置文件。
3.2 场景一:只想筛选导入相关的规则
假设团队最近在规范import导入组织方式,想让导入排序、未使用的导入都检查起来。第一步先用前缀筛选查看I系列规则:
ruff list --select I执行之后,终端会列出所有I开头的规则,主要就是isort导入排序相关的那几条。如果你想顺带看看F系列里和“未使用”相关的导入规则,可以继续筛选:
ruff list --select F4这里的F4是F系列里一个子分组,覆盖未使用导入、import * 这类问题。两步操作下来,导入规范相关的候选规则基本都在眼前了。
3.3 场景二:单独把命名规范筛选出来
命名检查是Ruff里最需要谨慎启用的一类,因为不同项目的命名习惯差异很大。有的团队坚持函数名全小写下划线,有的团队部分地方允许驼峰。先把N系列全部列出来看一遍:
ruff list --select N这会列出从类名、函数名、变量名到常量名等一整套命名规范规则。你可能会在里面看到N801这种类名规则,也可能看到N802这类函数名规则。这时候逐条判断团队到底需要哪几条,而不是盲目地把整个N系列导入配置。
实操中我的做法是:先把N系列全看一遍,然后用ignore把不适用的部分剔掉,而不是逐条往select里加。因为命名规则往往“整体是一套逻辑”,局部排除比局部挑选更符合语义。
3.4 场景三:由报错信息反查规则代码
还有一种更常见的情况:你在IDE或者CI日志里看到Ruff报了一个提示,觉得这个检查很合理,想把同类规则都打开,但不知道它的规则代码。这时候直接用关键词反查。
比如日志里提示“Do not use mutable data structures for argument defaults”,想确认这是哪条规则,执行:
ruff list --select B | grep -i mutable很快就能定位到B006,对应规则名mutable-argument-default。确认之后,如果你觉得这类bug隐患检查有用,再把整个B系列过一遍,挑几张合适的放进配置。反查过程比翻文档高效得多,因为Ruff规则名本身就是很好的关键词。
3.5 场景四:多类别组合筛选并验证
当你最终确定了几个大方向,比如格式、逻辑、导入、命名、bug隐患,可以一次性组合筛选:
ruff list --select E,F,I,B,N这个命令的输出就是将来你可能要启用的完整规则集合预览。看到结果没问题后,下一步临时验证:
ruff check --select E,F,I,B,N .注意这里用的是ruff check,后面跟了一个.,表示对当前目录做实际检查。你会立刻看到这个“规则大礼包”在真实代码上的表现,哪些文件报了哪些问题一目了然。
验证通过后,把同样的select写进配置文件,然后去掉命令行参数,日常执行ruff check .就会自动按配置筛选规则了。
3.6 场景五:在默认规则基础上临时追加
这里有个特别容易踩的坑:Ruff在没有配置select时,默认只启用一部分规则,大致是E4、E7、E9和F系列中的部分内容。当你第一次在命令行执行--select E,F,I,B,N时,这个选择会覆盖默认规则,而不是追加。也就是说,你等于把默认的E4、E7、E9都丢了,换成了完整的E系列,检查结果会突然变得“严格很多”。
如果你只是想临时在默认基础上追加一组命名规则,正确写法应该是把默认的前缀也写进去:
ruff check --select E4,E7,E9,F,N .这种做法在排查问题时很实用,因为你不想因为少写了默认规则而误判“新规则导致一堆新报错”。理解这个覆盖语义之后,你再看配置文件里的select,就会明白那是一个“完整清单”,不是“追加清单”。
4. 常见问题与排查实录
4.1 把N理解成数字或漏掉大小写
“--select N”里那个N,最大的误区就是理解成某一个数字编号。我第一次看这个命令时也觉得奇怪,为什么select后面跟个数字?实际这个N是规则前缀,或者说是一个“匹配模式”。如果你执行--select 5,Ruff会直接提示找不到匹配规则,因为规则代码不可能以数字开头。
另一个高频小问题是大小写。Ruff的规则代码前缀是大写字母,比如F401,小写f401不会被识别。虽然ruff list输出的列表里全部是大写,但手敲命令时下意识打成小写的情况并不少见。遇到 “no rules matched” 之类的提示,先检查大小写。
4.2 搞混 list 和 check 的职责
我见过有人把ruff list --select E501当成“检查代码里有没有E501问题”来用,执行完发现只是打印了一条规则信息,没有检查结果,觉得命令坏了。其实这两个命令分工完全不同:
| 命令 | 职责 | 典型用法 |
|---|---|---|
ruff list --select N | 查看/筛选规则库信息 | ruff list --select I |
ruff check --select N | 按N规则实际检查代码 | ruff check --select I . |
list是“菜单”,check是“上菜后试吃”。筛选规则的完整工作流是:在list里确认规则,到check里验证效果,最后写入配置。分不清这两步,会让整个过程混乱不少。
4.3 select覆盖导致“规则突然变少”
配置好select之后,如果你在命令行再执行一次--select,Ruff会用命令行参数覆盖配置里的select。这样会导致一个现象:本地排查时选了一组临时规则,排查完忘了关,第二天跑ruff check发现结果和CI完全不一样。CI没有命令行参数,用的是配置文件里的select;本地带参数,覆盖了配置。
排查这类问题的方法很简单:执行ruff check --show-settings .,Ruff会打印出实际生效的配置,包括select和ignore。先看这里,再对照命令行参数,基本一查一个准。
4.4 规则代码不存在时的报错处理
如果select里写了一个不存在的规则代码,Ruff会有清晰的报错信息,指出 “rule ...” 不存在。常见原因有两个:一是拼写错误,比如把I001写成I002;二是想用的规则属于需要开启preview功能的“预览规则”。Ruff的规则有稳定和非稳定之分,部分新规则需要加--preview参数或配置preview = true才生效。遇到这种情况,先执行ruff list | grep 关键词确认规则代码是否真实存在,再检查这条规则是否标注了preview标志。
4.5 高效筛选规则的小工具组合
如果六百多条规则还是太多,我通常会组合一些小工具提高筛选效率:
ruff list | less,分页浏览全部规则,适合第一次看规则库时建立整体认知。ruff list | grep -i "import",按关键词过滤规则名,快速定位主题相关规则。ruff list --select E,按前缀过滤,适合已知分类后的精确浏览。ruff list --select B --ignore B006,先选后排除,预演最终生效的规则集合。
这一套组合下来,再大的规则库也能在几分钟内完成一轮筛选。我自己的做法是先把规则库整个看过一遍,然后把常用前缀记下来,之后几天之内就能把整个项目的Ruff规则配置稳定下来。
5. 最后说点实际操作中的体会
Ruff的设计哲学在“规则筛选”这件事上体现得很彻底:它先把所有规则透明地摊开,再给你一个足够灵活的参数去组合、预览、排除。ruff list --select N这个命令只是入口,真正核心的是前缀匹配逻辑和“先查看、再验证、后持久化”的工作方法。我自己最终给老项目定的配置是E、F、I、B、N五类为主,再配合少量S系列安全规则,代码质量检查在CI里跑得很稳定,开发过程中的噪音也明显减少。如果你现在正对着六百多条规则发愁,我的建议是不要一上来就想着全开或全关,先花十分钟把ruff list --select的几个用法各试一遍,你的项目需要什么规则、不需要什么规则,很快就会有答案了。