跑一下 ruff check,满屏的红线就出来了。不是说代码跑不起来,而是你突然发现自己这些年在 Python 里积攒的那些小毛病,被它一个个揪了出来,场面相当壮观。更让人头疼的是,这些问题平时用 flake8、isort、black 分着跑,感觉不痛不痒。到了 Ruff 这里,它一把梭哈,速度快得像没干活儿,但该挑出来的问题一个都没少。
最开始我也不太信。Python 圈子里“一个工具替代一堆工具”的说法,通常都带着点营销的味道。但真正上手之后才发现,Ruff 不是来讲概念的,它是实实在在地来帮你缩短 CI 时间,顺手收拾掉代码里的坏味道的。
日常项目里最烦人的,往往不是没有规范,而是规范太分散、工具链太长。
一会儿要用 black 格式化代码,一会儿要用 isort 整理 import 顺序,一会儿还得跑 flake8 检查代码风格。要是再补点 pyupgrade、pylint 的规则,本地开发环境和 CI 跑出来的结果还可能不一致,平添不少沟通成本。
Ruff 的做法就很直接:它把上面这些常见的活儿都揽到自己身上,而且速度还快得离谱。你只要跑一次就能感受到,这种工具带来的不是“理论上能提升效率”,而是让你在本地写完代码后,顺手扫一遍都觉得不麻烦。
先把它装上:
pip install ruff
在项目根目录直接运行检查命令:
ruff check .
很多人第一次跑会看到类似的输出:
F401 `json` imported but unused
I001 Import block is un-sorted or un-formatted
UP035 `typing.List` is deprecated, use `list` instead
这几个提示都很有代表性:没用的 import、import 顺序混乱、使用了旧版语法。单独看都不是什么大事故,但代码库一旦庞大起来,这些边角料会越积越多,最后 code review 的时候看着就别扭。
举个例子,下面这段代码在团队协作中太常见了:
import os
import json
from typing import List
import sys
def load_users(path: str) -> List[str]:
with open(path, "r", encoding="utf-8") as f:
return [line.strip() for line in f if line.strip()]
代码能跑,功能上也没毛病。但 Ruff 一扫,至少能看出两个问题:os、json、sys 里有没被使用的模块,而 List[str] 这种写法在新版本的 Python 里也没必要再保留了。
想让 Ruff 直接修复这类问题,可以加上 --fix 参数:
ruff check . --fix
它会自动处理一部分机械性的修改。修复后,上面的代码大概会变成这样:
def load_users(path: str) -> list[str]:
with open(path, "r", encoding="utf-8") as f:
return [line.strip() for line in f if line.strip()]
这种感觉就很对路。它不是在教你如何写代码,而是先把这些不值钱但又碍眼的小问题清理出去,不占用你的思维带宽。
更进一步,Ruff 现在连代码格式化的活儿也能干了:
ruff format .
比如你临时写了一段脚本,功能没问题,但排版确实有点乱:
def build_payload(order_id,user_id,items):
return {"order_id":order_id,"user_id":user_id,"items":[{"sku":i["sku"],"num":i["num"]} for i in items if i["num"] > 0]}
让 Ruff 格式化之后,代码的清晰度立马提升:
def build_payload(order_id, user_id, items):
return {
"order_id": order_id,
"user_id": user_id,
"items": [
{"sku": i["sku"], "num": i["num"]}
for i in items
if i["num"] > 0
],
}
这类工作以前通常交给 Black。现在 Ruff 也能胜任,至少在不少项目里,工具链确实可以再精简一层。
我的建议是,刚开始使用时别贪多求全,先把规则收紧到你真正会用到的那几类。在 pyproject.toml 里进行如下配置就够用了:
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
这些规则代号不用死记硬背。大概知道它们的职责范围就行:
E/F 系列检查基础代码问题。
I 负责 import 排序和整理。
UP 会提醒你不要再写旧版本的语法。
B 则会揪出一些容易埋坑的 Bug 类写法。
例如下面这段代码,我在线上脚本里见过不止一次:
def append_log(msg, bucket=[]):
bucket.append(msg)
return bucket
新手喜欢这么写,老手忙起来也可能顺手写出来。问题在于默认参数 bucket=[] 这个空列表在函数定义时就被创建,并且会在多次调用中被复用,导致日志会越加越乱。Ruff 对这类问题就不再是简单的“风格建议”了,它是在帮你拦截潜在的 Bug。
改成下面这样才更稳妥:
def append_log(msg, bucket=None):
bucket = [] if bucket is None else bucket
bucket.append(msg)
return bucket
Ruff 真正发挥价值的场景,不是你写个小 demo 的时候。而是在项目人多、提交频繁、CI 流水线已经有点跑得慢的时候,用它会特别顺手。
本地开发时,你可以这样组合使用命令:
ruff check . --fix && ruff format .
在提交流程中,还可以通过 pre-commit 钩子来自动化检查。在 .pre-commit-config.yaml 中添加如下配置:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.0
hooks:
- id: ruff-check
args: [--fix]
- id: ruff-format
这样一来,很多低级问题在 commit 之前就被拦截掉了,review 的时候就不用再为“import 顺序不对”、“这个变量没用到”这类小事反复沟通,大幅提升 CI/CD 流程的效率。
说到底,Ruff 最值钱的地方,不在于它有多“神奇”。而在于它把 Python 项目里那一堆零碎、重复、机械、但又不得不做好的事情,整合得相当利索。速度快,接入简单,给出的结果也清晰直接。一个工具能做到这个程度,对于优化开发工作流来说,已经非常够用了。
至于它未来会不会把所有老牌工具都替代掉,我倒不急着下结论。但至少在不少中小型项目、内部脚本、甚至一部分服务端仓库里,Ruff 已经从“可以试试”变成了“早点用上,能省下很多碎片时间”。这东西一旦用顺手了,再回头去看那套东一榔头西一棒子的检查链路,是真的会觉得麻烦。如果你也在为项目代码规范和工具链效率发愁,不妨到 云栈社区 的开发者板块和大家交流一下实战心得。