Ruff CLI 实战指南:从安装配置到 CI 集成的完整流程
发布时间:2026/10/29 15:20:38 锦皓数字建站

1. 为什么我劝你把 Ruff CLI 真正用起来Ruff 这个工具刚出来的时候我其实没太当回事——Python 生态里格式化有 Black静态检查有 Flake8导入排序有 isort类型检查有 mypy各管一摊虽然配置起来烦但好歹用习惯了。直到有一次接手一个历史项目光是把 Flake8、isort、pylint 三套配置对齐就花了大半天跑一遍全量检查要等将近两分钟CI 上更是慢得让人想砸键盘。后来抱着试试看的心态把 Ruff 接进去同样的代码库全量检查加自动修复不到两秒跑完那一刻我就知道这玩意儿回不去了。Ruff 是用 Rust 写的 Python 静态分析和代码格式化工具定位很明确把过去分散在多个工具里的活用一个二进制、一套配置、一次扫描全部干完。它兼容 Flake8 的大量规则集、isort 的导入排序逻辑、pyupgrade 的语法升级建议还自带格式化能力对标 Black。对日常写 Python 的人来说它解决的核心痛点就三个速度慢、配置散、工具链长。这篇文章我打算把 Ruff CLI 从最基础的安装调用到配置文件怎么写、规则怎么选、CI 里怎么接、踩过的坑怎么绕完整捋一遍。适合刚听说 Ruff 想上手的同学也适合已经在用但只停留在ruff check层面的朋友——命令行里藏着的细节比想象中多。我写这篇东西的出发点很简单官方文档虽然全但它是按功能罗列的缺少一条“从零到日常使用”的主线。我把自己这一年多在不同项目里用 Ruff 的经验按使用顺序重新组织配上实际参数和配置片段你照着抄就能跑起来。2. Ruff CLI 的整体设计与命令结构拆解2.1 一个二进制搞定检查与格式化这个设计取舍值不值Ruff 把所有能力收进一个可执行文件通过子命令区分功能。核心就两个ruff check负责静态分析和自动修复ruff format负责代码格式化。这种“单二进制多子命令”的设计和 Git、Cargo 是一个路子好处很直接——安装一个东西记住一套参数风格不用在多个工具之间切换心智模型。但这里有个容易被忽略的取舍Ruff 的检查lint和格式化format是两套独立的规则体系。ruff check管的是代码质量规则比如未使用变量、导入顺序、复杂度告警ruff format管的是纯排版比如缩进、引号风格、换行位置。两者互不干扰你可以只用其中一个也可以两个都用。我见过有同学以为跑了ruff format就等于做了代码检查结果提交上去一堆未使用导入这就是没搞清楚职责边界。为什么这么设计因为格式化和静态检查的诉求本质不同。格式化追求确定性——同样的代码进去永远同样的排版出来不应该有“建议”这种模糊地带静态检查则要给出可配置的规则集不同项目对严格程度的要求天差地别。把两者拆开各自演进配置也分开管理逻辑上更干净。实际用下来我建议两个都开但要在 CI 里分两步跑后面会细说。2.2 子命令全景除了 check 和 format 还有哪些值得关注大多数人日常只用check和format但 Ruff CLI 还有几个子命令在特定场景下很有用。ruff rule用来查某条规则的具体说明比如你想知道F401到底管什么直接ruff rule F401就能看到详细解释和反例正例比翻文档快。ruff linter列出所有支持的 linter 前缀ruff clean清缓存ruff version看版本。还有一个ruff analyze在较新版本里提供更细粒度的分析能力不过日常用得少。我特别想提ruff rule这是排查“这条规则为什么报”的神器。有次同事问我为什么他的代码被标了个B006我直接让他跑ruff rule B006输出里写得明明白白可变默认参数的问题还给了错误写法和正确写法对照。这种“规则即文档”的设计省掉了大量搜索时间。命令的通用参数也值得说。--config指定配置文件路径--isolated忽略所有配置只按默认跑排查配置冲突时好用--no-cache禁用缓存--verbose输出详细日志。这些参数在check和format上都通用记住一次到处能用。2.3 退出码与输出格式CI 集成的关键约定Ruff 的退出码约定很清晰0表示没有发现问题1表示发现了可修复或不可修复的问题2表示 Ruff 自身出错比如配置写错了。CI 里判断构建是否通过靠的就是这个退出码所以千万别在脚本里把ruff check的返回值吞掉。输出格式方面默认是人类可读的文本带文件名、行号、列号、规则码和说明。但 CI 里更常用的是--output-format参数支持concise、full、json、junit、github等。接 GitHub Actions 时用github格式问题会直接以注解形式标在 diff 上review 体验好很多接其他 CI 平台用json自己解析也行。这个细节很多人不知道白白浪费了 Ruff 和 CI 平台的集成能力。3. 安装与基础命令实操从零跑通第一条检查3.1 安装方式怎么选pip、pipx 还是独立二进制安装 Ruff 有三条主流路径选哪条取决于你的使用场景。项目内依赖用pip install ruff写进requirements-dev.txt或pyproject.toml的 dev 依赖组保证团队每个人版本一致全局工具用pipx install ruff隔离环境不污染系统 Python追求极致启动速度或者 CI 镜像精简直接下独立二进制解压即用连 Python 都不需要。我个人的习惯是项目里锁版本本地开发用项目虚拟环境里的那个CI 里也用同一个版本。为什么强调锁版本因为 Ruff 迭代很快规则集和默认行为在不同版本间可能有变化今天 CI 过了明天挂掉八成是版本漂移。在pyproject.toml里写死ruff0.x.y比什么都稳。# 项目内安装推荐 pip install ruff0.6.9 # 全局工具安装 pipx install ruff # 验证安装 ruff --version注意不要同时用 pip 全局装一个、项目虚拟环境里又装一个然后搞不清ruff命令到底调的是哪个。用which ruff确认路径或者干脆统一走虚拟环境。3.2 第一次运行 check默认规则集能查出什么装好之后进到任意 Python 项目根目录直接跑ruff check .。默认情况下 Ruff 启用的是Epycodestyle 错误和FPyflakes两组规则覆盖了最常见的语法级问题未使用导入、未定义变量、重复定义、f-string 占位符不匹配等等。这个默认集是刻意保守的保证开箱即用不会满屏红。第一次跑大概率会看到一堆F401 imported but unused。别慌这正是 Ruff 的价值——它帮你把历史遗留的垃圾导入一次性揪出来。先别急着--fix用ruff check . --statistics看一眼按规则码聚合的统计心里有个数哪类问题最多是不是集中在某几个文件。这个统计视图在评估一个陌生代码库的健康度时特别好用。# 基础检查 ruff check . # 按规则聚合统计 ruff check . --statistics # 只看某个目录 ruff check src/3.3 自动修复的正确姿势--fix 与 --unsafe-fixes 的边界ruff check --fix会自动修复所有被标记为“安全可修复”的问题比如删掉未使用导入、调整导入顺序、把 None改成is None。这些修复不改变代码语义放心跑。但有一类修复被标记为“不安全”比如把dict()调用改成{}字面量——大多数情况等价但如果你重载了dict这个名字语义就变了。这类修复默认不执行需要显式加--unsafe-fixes。我的经验是日常开发只开安全修复--unsafe-fixes只在明确知道后果时用且必须配合代码审查。曾经有同事图省事直接--fix --unsafe-fixes一把梭结果一个模块里自定义了同名函数被改出了隐蔽 bug测试没覆盖到上线才发现。安全修复和不安全修复的边界是 Ruff 团队反复权衡过的尊重这个边界就是尊重自己的睡眠。# 安全修复 ruff check . --fix # 包含不安全修复谨慎 ruff check . --fix --unsafe-fixes # 修复后重新检查确认 ruff check .3.4 format 子命令和 Black 的兼容性与差异ruff format .的定位是 Black 的替代品官方宣称在绝大多数情况下输出与 Black 一致速度却快一个数量级。实测下来对于一个中等规模项目Black 格式化要几秒Ruff 基本是瞬间完成。差异主要出现在少数边缘 case比如某些复杂表达式的换行策略、魔术尾逗号的处理。如果你是从 Black 迁移过来建议先在一个分支上跑一遍ruff format用git diff看看改动范围确认没有意外再合并。ruff format --check是 CI 里的标准用法只检查不修改有不符合格式的文件就返回非零退出码。--diff参数更直观直接打印出会做的修改适合在 review 时给人看。这两个参数配合使用CI 里既能把关又不产生副作用。# 格式化整个项目 ruff format . # CI 中只检查 ruff format --check . # 查看会改什么 ruff format --diff .4. 配置文件与规则选择让 Ruff 贴合你的项目4.1 pyproject.toml 里的 ruff 配置段怎么写Ruff 的配置统一放在pyproject.toml的[tool.ruff]段下或者独立的ruff.toml。我倾向放pyproject.toml一个文件管所有工具配置清爽。最基础的配置长这样[tool.ruff] line-length 100 target-version py311 exclude [.venv, build, dist, migrations] [tool.ruff.lint] select [E, F, I, UP, B] ignore [E501] [tool.ruff.format] quote-style double indent-style spaceline-length设成 100 还是 88Black 默认看团队习惯关键是和格式化、检查保持一致别检查按 100 格式化按 88那就自相矛盾了。target-version告诉 Ruff 你的最低 Python 版本它据此决定哪些语法升级建议是安全的——比如你写py38它就不会建议你用py310才有的 match 语句。exclude把虚拟环境、构建产物、数据库迁移文件排除掉迁移文件通常是自动生成的没必要检查。4.2 规则集选择策略从保守到激进的渐进路线select里放哪些规则前缀直接决定 Ruff 的严格程度。我的建议是分阶段来别一上来就全开。第一阶段用E、F、I导入排序、UP语法升级这四组基本无争议修复也安全。第二阶段加Bbugbear抓常见陷阱、SIM简化建议、C4推导式优化。第三阶段再考虑ANN类型注解强制、D文档字符串、PLpylint 规则集这类主观性强的。为什么强调渐进因为一次性开太多规则历史代码会瞬间爆出几千条告警团队直接放弃治疗。正确做法是先把select设成目标集合然后用per-file-ignores或者ignore把暂时不想管的规则压下去随着代码逐步清理再放开。这个过程我在三个项目里走过每次都是“先止血、再治病”。阶段推荐规则前缀适用场景保守E, F, I, UP新项目起步、历史项目初次接入进阶加 B, SIM, C4, RUF代码质量有一定基础严格加 ANN, D, PL, PT对类型和文档有强要求的团队4.3 per-file-ignores 与 noqa局部豁免的正确用法再严格的规则集也有需要豁免的地方。测试文件里大量assert会触发S101__init__.py里的未使用导入是故意的为了暴露公共 API这些都需要局部豁免。Ruff 提供两个层次配置级的per-file-ignores和代码级的# noqa注释。per-file-ignores写在配置里按文件路径模式匹配适合成批的、有规律的豁免[tool.ruff.lint.per-file-ignores] __init__.py [F401] tests/**/*.py [S101, ANN201]# noqa写在代码行尾适合单点的、临时的豁免。# noqa: F401精确豁免某条规则# noqa裸写豁免该行所有规则。我强烈建议永远写具体的规则码裸# noqa等于关掉了这一行的所有保护后患无穷。还有个细节# noqa只对同一行的告警生效如果告警报在上一行比如多行表达式的起始行注释位置要放对否则不生效——这个坑我踩过不止一次。4.4 缓存机制与 --no-cache 的使用时机Ruff 默认开启缓存把文件内容和检查结果的映射存在.ruff_cache目录。第二次跑同样的检查没改过的文件直接读缓存速度飞快。这个机制在本地开发时体验极好但在两种情况下会坑人一是你改了配置文件但缓存没失效极少见Ruff 会检测配置变化二是 CI 里缓存目录被错误地跨分支复用。排查“明明改了配置却不生效”这类玄学问题时第一反应应该是ruff check . --no-cache。如果加了--no-cache结果就对了那问题出在缓存。CI 里我一般建议把.ruff_cache加进.gitignore并且不要跨 job 共享宁可慢一点也要结果确定。5. 进阶用法与 CI 集成实战5.1 pre-commit 钩子配置提交前拦截问题把 Ruff 接进 pre-commit能在代码提交前就拦下问题比等到 CI 失败再回头改高效得多。配置写在.pre-commit-config.yamlrepos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.9 hooks: - id: ruff args: [--fix] - id: ruff-format注意rev要和项目里锁的 Ruff 版本对齐否则本地钩子和 CI 用的版本不一致会出现“本地过了 CI 挂”的尴尬。ruff钩子带--fix自动修复ruff-format钩子负责格式化。两个钩子的顺序有讲究先 lint 修复可能改动导入顺序再 format统一排版顺序反了可能 format 完又被 lint 改动导致需要二次提交。提示pre-commit 钩子只检查暂存区的文件全量检查还是得靠 CI。别以为本地钩子过了就万事大吉。5.2 CI 流水线里的两步走check 与 format 分开跑CI 里我坚持把检查和格式化分成两个独立步骤而不是合并成一条命令。原因有二一是失败时能一眼看出是质量问题还是格式问题排查路径清晰二是格式化检查可以用--check只读模式不产生任何文件改动流水线更干净。# 伪代码示意适配任意 CI 平台 steps: - name: Lint run: ruff check . --output-formatgithub - name: Format check run: ruff format --check .--output-formatgithub让问题直接以注解形式出现在 PR 的 diff 上reviewer 不用点进日志就能看到哪行哪列有问题。如果你的 CI 平台不支持 github 格式用json输出再自己解析或者退回默认文本格式也行关键是别把退出码吞掉。5.3 增量检查与全量检查的取舍大项目全量跑 Ruff 虽然快但也不是没有成本。日常开发我建议只检查改动的文件pre-commit 钩子天然就是增量的。CI 里则应该全量跑因为增量检查会漏掉“改动 A 文件导致 B 文件出问题”这种跨文件影响——虽然 Ruff 的规则大多是文件内分析但导入相关的规则确实可能受其他文件影响。有个折中方案CI 里对 PR 做增量检查加速反馈对主分支合并做全量检查兜底。具体实现依赖 CI 平台的能力核心思路是“快速反馈”和“完整把关”分开。这个策略我在一个上万文件的项目里用过PR 检查从几分钟降到几十秒体验提升明显。5.4 与编辑器集成保存即格式化的配置要点VS Code 里装 Ruff 官方扩展把默认格式化器设成 Ruff开启保存时格式化基本就齐活了。关键配置项是editor.formatOnSave和editor.codeActionsOnSave后者可以配置保存时自动执行ruff check --fix的安全修复。这样写代码的过程中导入自动排序、未使用变量自动清理几乎感觉不到工具的存在。这里有个细节值得说编辑器集成用的是 Ruff 的语言服务器模式和命令行共享同一份配置。所以你在pyproject.toml里改的规则编辑器里立刻生效不用重启。但如果配置写错了编辑器可能静默失败表现是“格式化没反应”。遇到这种情况先在命令行跑一遍ruff check .配置错误会直接报出来比在编辑器里瞎猜快得多。6. 常见问题与排查技巧实录6.1 规则冲突与误报怎么判断该改代码还是改配置Ruff 报的告警不是圣旨有些规则在特定上下文里就是误报。判断标准很简单如果这条告警指向的代码确实有隐患改代码如果代码是刻意为之且合理改配置豁免。比如B008报“函数调用作为默认参数”大多数情况是真问题默认参数在定义时求值一次但如果那个调用是Depends()这类框架的依赖注入标记就是合理用法该豁免。我见过有人为了消掉一条误报把代码改得面目全非这就本末倒置了。Ruff 是工具工具服务于代码不是反过来。遇到拿不准的规则先ruff rule code看官方解释和示例再决定。6.2 格式化后 diff 爆炸迁移时的分批策略从 Black 或 autopep8 迁移到ruff format第一次全量格式化可能产生巨大的 diffreview 根本没法看。我的做法是单独开一个“纯格式化”提交提交信息写清楚“仅格式化无逻辑改动”让 reviewer 知道这个 commit 不用逐行看。然后在这个提交之上再做功能改动。这样 git blame 的时候也能一眼区分格式改动和逻辑改动。如果项目特别大还可以按目录分批格式化每次一个子目录逐步推进。虽然麻烦点但每次 diff 可控出问题容易回滚。6.3 版本升级导致的规则变化锁定与灰度Ruff 版本升级偶尔会带来默认行为变化比如某条规则从默认关闭变成默认开启或者修复策略调整。这就是为什么我一直强调锁版本。升级时不要直接改版本号了事先在本地跑一遍全量检查对比升级前后的告警数量差异评估影响面。如果差异大看看是不是新规则导致的用ignore临时压住再安排时间逐步清理。常见现象可能原因排查动作改了配置不生效缓存未失效加--no-cache重跑CI 过了本地挂版本不一致对比ruff --version格式化没反应配置语法错误命令行跑ruff check .看报错告警数量暴涨版本升级引入新规则对比升级前后--statisticsnoqa 不生效注释位置不对确认注释在告警所在行6.4 性能调优大仓库下的检查加速手段Ruff 本身已经很快但在超大仓库里还是有优化空间。第一确保exclude配置到位虚拟环境、构建产物、第三方 vendored 代码统统排除别让 Ruff 去扫不该扫的东西。第二善用缓存本地开发别动不动--no-cache那是排查问题用的不是日常用的。第三CI 里如果支持并行 job可以按目录拆分检查任务虽然 Ruff 单进程已经够快但超大仓库拆分后反馈更快。还有一个容易忽略的点target-version设得越准确Ruff 能做的优化越多。如果你实际用 py311 但配置写的 py38Ruff 会保守地不用一些新版本才有的分析能力。把这个配对准算是免费的性能提升。7. 我踩过的坑和几条实在建议说几个只有真用过才会知道的细节。第一ruff check --fix和ruff format的执行顺序会影响最终结果我的习惯是先check --fix再format因为 check 的修复可能引入新的格式问题比如删掉导入后留下空行format 收尾正好抹平。第二# noqa注释在格式化后可能位置漂移如果某行同时有长表达式换行和 noqa格式化后 noqa 可能跑到错误的行上导致豁免失效——这种 case 建议把表达式拆短别依赖 noqa 的位置。第三团队协作时配置文件一定要进版本控制而且要在 README 或者贡献指南里写清楚“提交前请跑 pre-commit”。我见过项目配了钩子但没人装等于白配。第四别迷信“规则开得越多越好”规则是为代码服务的一个团队连E和F都没清理干净就上ANN强制类型注解只会制造抵触情绪。循序渐进让工具成为助力而不是负担。最后分享一个我常用的组合命令日常开发收尾时跑一遍基本能保证提交质量ruff check . --fix ruff format . ruff check .先修复、再格式化、最后复查确认干净。三步串起来一条命令搞定。这个习惯坚持下来代码库的整洁度会有肉眼可见的提升。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。