“代码能跑”和“代码在下一个修改者手里不挨骂”,往往隔着十万八千里。我第一次意识到这件事,是有一次捡起一份半年没人动过的遗留代码,自信满满地在本地跑了一遍 Pylint,结果大半个终端都是 C 类和 W 类告警。当时我差点把 Pylint 从项目里删掉,毕竟在很多人眼里,工具只会告诉你代码不够好,而代码不够好这件事,我们又不能靠写文档解决。可冷静两天后我又装了回来:我真正受不了的,不是它能挑出问题,而是我从来不知道这些问题有多碎、多密集、多容易在改动之后悄悄送回主干。
这篇文章说的就是 Pylint 和 Flake8 这对组合,怎么在 Python 项目里充当持续不断的“代码质量雷达”。它适合这些读者:想给团队加一道最低门槛、又怕配置太复杂做不下去的人;经历过 Code Review 时因为“这行太长”“这个 import 没用到”“这个分支真的不复杂吗”来回拉扯的人;也适合已经准备引入静态检查工具,但听到“还得写 .pylintrc”就打了退堂鼓的个人开发者。我会把实际运行一遍之后看到的警告、没有看文档踩过的坑、以及最终沉淀到 CI 里的那套配置,都摊开来说清楚。
1. 先面对现实:Pylint 和 Flake8 到底在“骂”什么
1.1 静态检查查的不是 bug,是坏味道
静态检查工具的定位,很难用一句话讲清楚。它不像测试那样直接验证一条输入路径是否正确,也不像类型标注一样约束实参和返回值的形状,它做的是两件事:第一,找出代码里明显“没收拾干净”的痕迹;第二,在一个很久没人说话的代码库里,用机器可执行的方式提醒你,这里将来可能会让某个人拍桌子。
Pylint 的报错分成几类,代码里经常看到 E、W、C、R、F 这些开头。E 是 error,这类问题通常是运行时会出事的,比如引用了不存在的变量、错误地调用了不存在的方法;W 是 warning,还没到立刻报警的程度,但往往带着隐患,比如被覆盖的表达式;C 是 convention,主要跟编码习惯有关,缺 docstring、命名不符合 PEP8、行首多了空格;R 是 refactor,意思是这段代码能跑,但“这样写会让后来的人想重写”。对一个新接入的项目来说,R 和 C 通常占大头,它们不会让你上线崩,但会让重构的人无从下手。
Flake8 则是一个更收敛的组合包,它把 pycodestyle、pyflakes 和 mccabe 三份检查工具绑在一起,既能检查出像“第 88 行第 21 个字符后面多了个空格”这种格式问题,也能检查出“这个变量赋值了但一次都没用”“这个 import 进来之后没被引用过”。它的检查维度看起来没有 Pylint 丰富,但胜在轻量、直接、好解释。
1.2 为什么不是只装其中一个
这个问题我一度也纠结了很久。只装 Pylint,它能覆盖 Flake8 里 pyflakes 的绝大多数问题吗?能,但 Pylint 输出太嘈杂,一百行代码跑到最后,真正重要的一两个 E 级别错误经常淹没在几十条 C 类风格建议里。只装 Flake8,它的风格检查又只关心表面格式,不会对“这个函数参数太多”“这朵烂花一样的分支嵌套迟早出事”这样涉及可维护性的问题给反应。
所以推荐把两者放在不同的“检查高度”里用:Flake8 处理编码规约的底线,Pylint 负责再往上走一层的逻辑味道。你可以把 Pylint 想象成一位稍微啰嗦的设计评审,它会提醒“你定义了方法但没 self 调用,是不是忘了注册到路由”,而 Flake8 更像是在门口拦人的保安,先确保每个人不要踩着奇奇怪怪的缩进进来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 初接入时的“视觉冲击”:从一屏告警开始认识工具脾气
2.1 第一次在项目里跑起来的配置门槛
接一个新项目,我不会一上来就写一堆自定义配置,而是先用默认配置跑一遍,让工具展示真实数据。安装方面其实没什么悬念,直接用 pip 装就好:
bash复制pip install pylint flake8
接着对包名或者整个源码目录分别运行:
bash复制pylint mypackage
flake8 mypackage/
如果项目里没有配置文件,命令行输出的结果往往会很“结实”:Pylint 会给你一个得分,比如 3.54/10,下面跟着一条条形如 mypackage/core.py:45:11: R1714: Consider merging these comparisons with 'in' 的消息;Flake8 的格式则是 core.py:45:13: F401 'json' imported but unused。两者都带着文件路径、行列号、消息 ID,这本身就说明它们的设计理念是一个给编辑器用、一个给命令行用,能让你在代码里精准落到出错位置。
2.2 常见的默认告警长什么样
只看说明不看实际,永远体会不到工具为什么惹人烦也讨人喜欢。以下是我在新代码里大概率会看到的三类默认告警:
python复制# 1. F401:json 被 import 进来,后续完全没用
import json
# 2. W0611:urllib 模块 import 之后从来未被使用
import urllib
def fetch_user(user_id):
# 3. R0913:参数超过 5 个,Pylint 认为这里复杂度开始上升
result = {
"user_id": user_id,
"status": 1,
"message": "ok",
"extra": None,
"trace": [],
}
return result
对于 project 里一个一百行的模块,未使用 import 往往出现过很多次。很多人会说“这又不影响运行时”,确实不影响,但它会误导后来的人,让人以为项目已经依赖某个
