资讯详情

Ruff替代Flake8与Black:Python代码检查与格式化工具实战

📅 2026/10/19 10:10:45 | 华诺云谱 👁 阅读
Ruff替代Flake8与Black:Python代码检查与格式化工具实战
说实话以前我每逢接到一个有点年头的Python项目前半个小时基本都耗在配置代码检查工具上。装Flake8、配Black、再弄isort等这三个家伙全部调好想看的代码早就被忘了。更别提Flake8那一套插件生态装多了彼此冲突装少了检查项缺失每次开会都被其他同事追问“你那个lint配置到底怎么搞的”。直到后来在某个模拟项目X里换成了Ruff才觉得这类工具本来就该长这样一个二进制文件一个配置文件linter、formatter、import排序全搞定跑起来还快得离谱。今天就把我这段折腾经历和踩过的坑完整写出来给正在做Python工程化改造的读者一份可照做的参考。Ruff是一个用Rust编写的Python静态检查与代码格式化工具核心定位是替代Flake8、Black和isort这类传统工具的日常工作流。它的执行速度比传统工具快一到两个数量级这也是多数人第一次用它时最直观的感受。这篇文章适合从零接触Ruff的新手也适合在团队里推工程化但被工具链复杂度折腾到崩溃的开发者参考。1. 为什么放弃Flake8 Black改用Ruff1.1 传统工具链的痛点插件冲突与速度短板先说说我原来那条工具链的工作方式。Flake8本身是一个聚合器真正干活的是它底下的pyflakes和pycodestyle前者负责逻辑错误检查后者负责风格规范检查。为了让代码风格更严格我又装了一堆插件比如检查命名规范的、检查复杂度指标的、强制类型标注的。每个插件都往Flake8里塞自己的规则但它们的配置项各搞一套有的认setup.cfg有的只认tox.ini有的还读取环境变量。有一次我在某跨平台系统里把一条规则在三个配置文件里各配了一遍改了一下午才发现某个插件只看其中一个简直想摔键盘。Black的问题倒不在配置而在于速度。纯Python写的格式化器在中等规模代码库上跑一次要十几秒在CI里每次提交都要等这么久体验非常割裂。isort单独用还行但它要解析Python语法来做import分类本质也是一个解析器加遍历器速度同样不理想。三个工具加在一起本地跑一轮lint加format的时间够我喝半杯咖啡了。1.2 Ruff的破局思路一个二进制解决所有问题Ruff的设计思路很直接既然都是解析Python源码那就用Rust写一个解析器把安全检查、风格检查、import排序、格式化全部装进同一个二进制里。这么做有个天然优势——源码只解析一次后面的所有检查都建立在同一棵语法树之上性能消耗自然比每个工具都重新解析一遍要低很多。另外一个务实的设计是规则兼容。Ruff没有重新发明规则体系而是直接实现了Flake8系列、pyflakes、pycodestyle、isort乃至pyupgrade的规则逻辑。我原来项目里用的Flake8配置迁移到Ruff时大部分规则代码不用变只需要改一下配置文件的写法即可。这一点对存量项目极其友好也是我在某公司代码库上能快速落地的关键原因。1.3 适用场景的判断标准Ruff并不是在所有场景下都能直接替换传统工具。如果你只是写一些一次性脚本那用不用Ruff都不影响结果。但当你的项目满足下面这几个特征时换到Ruff收益非常明显代码库规模中等以上文件数量超过一两百个原有的lint时间已经让人不适。团队对代码风格有强制要求希望把检查步骤放进CI和pre-commit流程里。项目正在从Python 2向Python 3迁移或者准备升级Python版本Ruff的pyupgrade规则可以帮助自动处理很多语法层面的替换。你在用VS Code或类似编辑器希望保存文件时立即看到检查结果而不是等外部工具慢悠悠跑完。反过来讲如果你的项目高度依赖某个Flake8冷门插件而这个插件暂时没有对应的Ruff规则那就要权衡迁移成本。我个人的做法是先把支持范围内的规则全部启用冷门需求单独保留一个轻量检查脚本等Ruff规则补全后再彻底切过去。2. 环境准备与快速上手2.1 安装Ruff的几种方式及选择Ruff的安装方式很丰富我试过四种各有适用场景。用pip安装是最通用的做法适合大多数Python项目pip install ruff安装完成后建议验证一下版本ruff --version如果你已经在用uv管理Python依赖可以用uv添加Ruffuv add --dev ruff用Cargo安装适合那些本来就在Rust工具链里的开发者cargo install ruff在一些无网环境的服务器上也可以直接下载官方预编译的二进制文件解压后放到PATH目录里就能用。我当时做某图像处理Demo的CI流水线时就是直接下载二进制省去了pip依赖解析时间。注意Ruff版本迭代很快不同版本的规则行为和默认配置会有差异。强烈建议在项目里锁定版本比如用ruff0.11.2这样的精确版本号避免某次升级后规则行为变化导致CI突然失败。2.2 第一条命令ruff check安装好之后进入项目根目录直接运行ruff check .Ruff会扫描当前目录下所有Python文件输出检查结果。我第一次在某个老项目上跑这个命令时输出结果直接刷满了一个终端窗口。但注意这里只是“检查”不会修改任何文件。所以你可以放心大胆地先摸清项目的家底。如果确认Ruff报的问题都是你想修的加上--fix参数让它自动修复ruff check . --fixRuff能自动处理的问题包括未使用的导入、未使用的变量、import排序、不规范的引号、多余的括号等。但注意不是所有问题都能自动修比如某些逻辑层面的错误它只能报出来需要你手动改。2.3 第一条命令ruff format接下来是格式化命令:ruff format .这条命令会对所有Python文件做代码格式化默认遵循的代码风格与Black基本一致。我的经验是第一次跑格式化时最好把改动单独提交一次不要混在其他功能修改里否则后续review代码时很难分辨哪些是格式化改动哪些是业务逻辑改动。2.4 解读输出信息Ruff检查时输出的格式非常直观以文件名:行号:列号: 规则代码 描述的方式展示。例如src/utils.py:12:5: F401 os imported but unused这里F401是规则代码它表示这是Flake8规则中的“未使用导入”。看到这类信息你需要做的判断是这个导入是确实没用还是因为某种动态调用方式而让工具误判。如果是动态调用可以在导入语句后面加一行# noqa: F401进行豁免。3. 核心配置解析3.1 配置文件的位置与格式选择Ruff默认会按顺序查找pyproject.toml、ruff.toml和.ruff.toml。如果项目里已经有pyproject.toml我建议把Ruff配置写进去因为这样可以统一管理工具配置。如果你不想碰pyproject.toml单独创建一个ruff.toml也完全没问题。配置的基本结构长这样[tool.ruff] line-length 88 target-version py311 [tool.ruff.lint] select [E, F, W, I, UP] ignore [E501, E203]这里的line-length是每行最大长度Ruff默认是88和Black一致。target-version表示目标开发Python版本Ruff会根据这个参数决定参数兼容性检查的规则行为。3.2 select、ignore与extend-select的区别初学者最容易搞混的就是这三个配置项。select用来指定启用哪些规则集合。如果完全用select会把之前选定的规则全部覆盖掉。extend-select是在已有规则基础上追加规则通常和代码库中预先存在的select配合使用[tool.ruff.lint] select [E, F] extend-select [B, SIM]ignore用来忽略指定规则。我经常看到有人把ignore当作extend-select的反向操作来用实际上它的优先级最高被忽略的规则无论怎么select都不会生效[tool.ruff.lint] select [ALL] ignore [E501, D203]3.3 规则类别对照表Ruff的规则代码体系很庞杂它复用了pydocstyle、pyupgrade、flake8-bugbear、flake8-simplify等许多生态的规则前缀。我在这张表里整理了自己最常用的规则类别方便按需选择。前缀来源主要检查内容E / Wpycodestyle风格错误与警告如E501行太长、W291行尾空格Fpyflakes逻辑错误如F401未使用导入、F841未使用变量Iisortimport排序与分组Npep8-naming命名规范如变量命名、函数命名风格UPpyupgrade语法升级建议如旧式类型写法转为新写法Bflake8-bugbear常见编码陷阱如可变默认参数SIMflake8-simplify代码简化建议如判断条件冗余C4flake8-comprehensions列表推导式相关建议Dpydocstyle文档字符串规范ANNflake8-annotations类型标注检查RUFRuff专属Ruff特有的规则如无效的noqa注释PLpylint移植自pylint的部分检查如果不想一个个去查每条规则的含义可以在项目里运行ruff rule F401Ruff会把这条规则的详细说明、示例代码和修复方式打印出来比在线查文档方便很多。3.4 按文件区分规则的per-file-ignores实际项目中经常需要针对不同目录使用不同的规则标准。比如__init__.py里常见的F401未使用导入在常规文件里应该警告但在__init__.py里可能只是为了重新导出模块可以单独豁免。Ruff用per-file-ignores解决这个问题[tool.ruff.lint.per-file-ignores] __init__.py [F401] tests/* [D, ANN]我一般在迁移老项目时会用per-file-ignores先豁免掉所有历史遗留问题比较集中的目录只在新代码目录上执行完整规则检查。等存量问题逐步清理干净再把豁免范围慢慢缩窄这种渐进式迁移方式对团队非常友好。3.5 formatter的额外配置Ruff的check和format共用同一套[tool.ruff]配置但各自也有独立配置段。格式化器常用配置如下[tool.ruff.format] quote-style double indent-style space skip-magic-trailing-comma false docstring-code-format truequote-style控制字符串的引号风格默认双引号。indent-style控制缩进是用空格还是制表符。docstring-code-format会格式化文档字符串里的示例代码块这个功能我一开始没注意到后来发现它能把docstring里凌乱的代码示范也统一格式非常贴心。提示skip-magic-trailing-comma这个参数值得特别说一句。Python的格式化器遇到列表或函数参数末尾有逗号时通常会把这个结构展开成多行。如果你希望保留手工换行的方式就把这个参数设为true。4. 实操过程与性能体验4.1 在中等规模代码库上迁移的真实体验我在某跨平台系统上做了一次完整迁移代码库大约有600多个Python文件总行数在20万上下。原来的Flake8跑完全量检查需要接近40秒isort单独跑需要15秒Black格式化需要25秒左右。换用Ruff后ruff check全量检查耗时大约2.8秒ruff format全量格式化耗时大约3.5秒。这个差距在本地开发时感知非常明显保存文件后的即时检查几乎感觉不到延迟。如果只是检查单个文件Ruff的反应速度基本是毫秒级。我在VS Code里打开任意Python文件保存后底部立刻就能看到检查结果弹出来而不是像以前那样还要等个一两秒。4.2 缓存与增量检查Ruff默认会做缓存第二次检查相同文件时会直接从缓存中读取结果速度更快。缓存默认存在~/.cache/ruff目录。如果你改了某些无关紧要的配置文件想强制重新检查可以用ruff check . --no-cache在CI环境中我建议不要开启--no-cache因为CI会创建临时目录有了缓存反而能加快重复执行速度。但要注意如果CI的环境每次都是全新的缓存反而没有意义。这种情况可以在CI里把Ruff的缓存目录指向固定路径让同一台机器上的多次构建共享缓存。4.3 用pyproject.toml做统一配置管理在推进团队工程化时工具配置统一是一件大事。原来项目里Flake8配置写在.flake8Black配置写在pyproject.tomlisort配置写在setup.cfg三个位置跨越了三种格式。换到Ruff之后全部内容收敛到一个文件里[tool.ruff] line-length 100 target-version py311 [tool.ruff.lint] select [ E, F, W, I, N, UP, B, SIM, C4, D, ANN, ] ignore [ E501, ] [tool.ruff.lint.per-file-ignores] __init__.py [F401, D100] tests/* [D, ANN] migrations/* [E402, F401, F811] [tool.ruff.format] quote-style double一个配置文件同时管住了代码风格检查和格式化行为新人加入团队后只需要看这一个文件就能理解全部规则约定。4.4 与pre-commit集成团队使用pre-commit管理git提交钩子时Ruff的接入非常简洁。新建.pre-commit-config.yaml写入repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.11.2 hooks: - id: ruff args: [--fix] - id: ruff-format这样每次git commit时Ruff会先自动修复能修复的问题再检查剩余问题然后ruff-format会格式化所有改动的文件。这两步都通过后代码才能进入提交流程。我个人的建议是在pre-commit里让Ruff只处理“本次提交涉及的改动文件”。这样不会因为存量问题阻塞正常提交团队推行时阻力会小很多。具体做法是在args里加上--diff参数其实不需要pre-commit默认只会把变更过的文件传给hook所以直接保持默认行为就行。4.5 与编辑器VS Code的集成细节VS Code里装好Ruff扩展后在settings.json里加上{ python.analysis.enable: true, [python]: { editor.defaultFormatter: charliermarsh.ruff, editor.formatOnSave: true } }配置完成后保存文件时Ruff会自动格式化并在“问题”面板中显示所有lint错误。我在实际使用中发现把editor.codeActionsOnSave里的source.fixAll.ruff设为true还能让保存时自动修复部分问题这个组合在大多数项目里都能明显减少手动修改量editor.codeActionsOnSave: { source.fixAll.ruff: true }不过我建议把formatOnSave和source.fixAll.ruff都打开后花一天时间观察一下改动幅度。有些人可能不习惯自动修改代码如果觉得改动太多可以先只开检查不开自动修复。5. 常见问题与排查技巧5.1 迁移后报错数量过多怎么办几乎所有人第一次在存量项目上跑Ruff都会面对刷屏级别的错误输出。这时候不要慌也不要试图一次性修完。我的做法是分三步走首先用select [F, E, I]只启用最核心的规则集先清掉未使用导入、明显语法级风格问题和import排序问题。这三类问题大多是机械性的Ruff的自动修复能很好地处理。接着把UP、B、SIM这些规则加上这轮会涉及一些重构操作比如简化循环、删除冗余分支。先跑--fix自动修复剩余的手动处理。最后再把D文档字符串、ANN类型标注这类侵入性强、需要写大量新代码的规则开启。这两类规则不建议在旧代码库上强制推行否则容易引起团队情绪反弹。我在某公司的项目里就是先让全量代码通过核心规则然后把文档相关规则限定在新代码文件上靠per-file-ignores逐渐收口。5.2ruff check和ruff format的执行顺序我见过不少团队在pre-commit里同时添加ruff和ruff-format但执行顺序反了导致either格式化或检查反复失败。正确的顺序是先做格式化再做lint检查。因为格式化会调整代码布局有些lint规则如行长度、行尾空字符在格式化前和格式化后的结果不同。如果先检查后格式化格式化结束后可能需要再跑一次检查才能确认。实际在pre-commit的hook顺序上把ruff-format放在ruff之后即可或者反过来也基本能过。但为了保证一致体验我在脚本里统一采用先ruff format再ruff check的顺序。5.3 格式化后代码被大量改动慌了怎么办ruff format一跑几百个文件同时发生变化这种情况几乎每个初次使用者都会遇到。但请相信Black风格的格式化策略是非常确定的同样的输入永远产生同样的输出。这意味着格式化的改动是一次性的后续只要人们不再手动写出风格迥异的代码Ruff格式化的差异就不会重复出现。我在实际项目中看到有些同事因为看到大量代码变动而感到不安我劝他们做这件事用git blame检查改动行逐行对比会发现大部分改动只是引号、缩进和换行位置的变化业务逻辑完全没有受影响。真正需要担心的反而是那些“格式化后语义变化”的情节但Ruff的格式化器在设计上非常保守不会触发这类问题。5.4 规则冲突的处理以E203和W503为例有些规则本身是互相矛盾的。最典型的例子是E203冒号前不应有空格和W503折行运算符应位于行首。Black格式化后的代码有时会在切片操作的冒号前加空格从而触发E203同时Black的行尾运算符又将折行运算符放在行首触发W503。黑格式自己的文档里就明确建议禁用E203和W503。在Ruff里绝大多数上游规则集合都默认忽略了E203但如果你手动用select [ALL]把所有规则打开这些冲突就会回来咬你一口。我通常在完整开启规则时会系统性地忽略掉这些与格式化器默认行为冲突的规则。5.5 Ruff的行为与Black存在哪些差异虽然Ruff官网宣称ruff format在设计上兼容Black但两者在一些边缘场景上确实存在差异。我遇到过几个例子Black对某些嵌套数据结构的分行处理更激进Ruff相对保守。对于yield语句里的括号处理两者换行逻辑不完全一致。对于逗号结尾的列表两者在“是否展开成多行”的判断上存在少量细节差异。如果你从Black切换到Ruff建议在新的格式化器上先跑一遍全量格式确认改动范围可控后再提交。如果团队对格式化风格有特别强的一致性要求可以在Ruff文档的格式差异列表里逐项确认。5.6 让Ruff检查import类型与范围最后分享一个我经常被问到的技巧。有些项目禁用了某些标准库模块或第三方库想在代码里强制不出现这些import。Ruff没有直接提供“禁用特定模块”的规则但可以用flake8-tidy-imports的规则来检查相对导入或用RUF里的一些规则针对性检查导入方式。如果要检查“禁止导入某个模块”可以用per-file-ignores反着思考在被禁止的模块路径上触发规则然后单独忽略某些例外文件。实际操作上我更推荐用自定义规则脚本配合Ruff来做这件事Ruff只负责风格与正确性禁止导入这种语义性检查最好还是交给人来设计规则。6. 一些值得保留的Ruff CLI技巧6.1 只显示错误忽略警告运行检查时如果提示信息太多可以加--quiet参数只显示检查结果不显示额外日志。如果只想关注错误级问题可以用--output-format concise或者配合--statistics统计错误分布ruff check . --statistics输出结果会按照规则代码分组统计出现次数这样你一眼就能看出哪些规则被违反得最多据此确定修复优先级。我在老项目迁移时第一次跑--statistics看到某个规则出现几百次就能提前和同事沟通好这类问题统一按批量自动修复处理。6.2 在CI中只检查新增的改动行如果你希望Ruff在PR检查中只检查新增代码可以使用--diff相关流程。常规做法是让Ruff从git diff中提取新增的行针对这些内容做检查。例如ruff check $(git diff --name-only --diff-filterAM -- *.py)这个方案只检查本次修改涉及的文件比全量扫描要快也让CI更聚焦于当前提交的质量。但要注意如果新增的代码依赖某个之前定义但未使用的import检查结果可能会有关于“重新定义未使用名称”的误报。这种情况在日常开发中并不常见我在遇到时通常会微调规则或增加局部豁免。6.3 利用ruff rule和ruff linter查询规则Ruff内置了两个命令帮助用户理解规则ruff linter这条命令会列出所有可用的规则集。想了解某条规则的具体行为用ruff rule B006输出会包含该规则的描述、错误示例和修复后的示例代码。这两个命令组合使用基本不需要频繁翻网络文档。7. 计量检查与团队推广的一点心得前面讲的都是技术细节最后聊一点非技术方面的经验。工具再强如果团队不买账推行阻力会非常大。我在某跨平台系统里推广Ruff时专门做过一个简单的操作先在一个相对独立的子项目里跑通Ruff展示从Flake8Black切换到Ruff后本地检查时间从一分钟缩短到几秒的实测对比再让团队成员亲自体验保存文件即反馈的编辑器集成效果。实际反馈普遍是“确实回不去了”。另外要注意的是切换格式化工具这件事最好不要和代码重构、bug修复混在一个PR里提交。Ruff的大规模格式化是一次性动作单独提交一次大改动后续再走增量提交流程会顺畅很多。我见过混着提交的惨痛教训review的人根本分不清哪一行是业务改动哪一行是格式化改动最后不得不用二分法重新拆提交浪费了大量时间。还有一个小经验Ruff默认的88字符行宽对大多数项目来说已经足够但如果你所在的团队长期使用100或120字符的行宽在配置文件里显式声明line-length比默认值更合理。不同项目没必要强行统一到一个数值我在个人项目和团队项目里就分别用了88和100。行宽不只是一个风格参数它直接影响代码的布局、表达空间和review时的换行习惯因此值得在团队内部认真讨论一次并记录到贡献指南里。从我自己的体验来看Ruff最大的价值是它省掉了Python工具链中大量无意义的等待时间把代码检查从“跑一次就烦一次”的负担变成一种实时反馈的流畅体验。如果你目前还在用Flake8加Black加isort的组合或者正在为CI里不断增长的lint时间发愁我建议直接在当前项目里装上Ruff跑一次对比实验。用数据说话比我在这里写一万字都管用。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑