Pylint 3.0 大版本发布全解析:新特性、破坏性变更与升级迁移指南

原创2026-10-12 10:22:321,734 阅读
文章标签:静态分析代码质量Lint开发工具

Pylint 3.0 大版本发布全解析:新特性、破坏性变更与升级迁移指南

Pylint 3.0 是 Pylint 历史上重要的主版本里程碑(2023-10-02 发布 3.0.0),它正式支持 Python 3.12,带来了全新的 json2 报告器、pyreverse 图表工具的显著增强,并集中执行了一批酝酿已久的破坏性变更与弃用清理。本文以仓库 doc/whatsnew/3/3.0/index.rst 为主线,结合 pylint 源码中的真实实现,系统梳理 3.0 系列(3.0.0 → 3.0.4)的全部新特性、行为变化、新增检查项与迁移要点,帮助你完成从 2.x 到 3.x 的平滑升级。

版本总览与发布节奏

Pylint 3.0 系列在 3.0.0 发布后迅速跟进了一系列补丁版本,主要围绕 Python 3.12 兼容性与误报修复:

版本 发布日期 主题
3.0.0 2023-10-02 主版本:Python 3.12 支持、json2 报告器、破坏性变更
3.0.1 2023-10-05 修复 inherit-non-class 误报、enum + dataclass 装饰器崩溃
3.0.2 2023-10-22 PEP 695 泛型语法误报、消息转义、inconsistent-quotes 等
3.0.3 2023-12-11 unnecessary-lambda、浅拷贝建议修正、目录递归遍历问题
3.0.4 2024-02-23 used-before-assignment、no-member、nested-min-max 等误报修复

版本 3.0.0 的几个关键全局事实(源码与发布说明共同确认):

  • Python 3.12 正式支持:运行环境要求 Python 3.8+,但被检查的代码可以基于更早的 Python 版本(通过 --py-version 指定)。
  • astroid 依赖升级到 3.0.0:性能与推断能力随之提升。
  • 代码完全类型化:Pylint now exposes its type annotations,仓库根目录也新增了 py.typed 标记文件。
  • 依赖精简:移除了 wrapt、setuptools 等依赖。
  • invalid-name 默认不再强制名称最短 3 个字符(详见下文"命名检查"一节)。

核心亮点一:全新的 json2 报告器

3.0 最重要的新功能之一,是新增了 json2 报告器。它相比旧的 json 报告器提供了更丰富、更易解析的结构化输出。在 pylint/lint/base_options.py 的 output-format 帮助文本中,json2 被明确标注为 "improved json format",官方建议优先使用它,旧的 json 报告器将不再维护。

使用方法

pylint my_module.py --output-format=json2
# 或使用短格式
pylint -f json2 my_module.py

输出结构示例

官方发布说明给出了如下示例输出:

{
    "messages": [
        {
            "type": "convention",
            "symbol": "line-too-long",
            "message": "Line too long (1/2)",
            "messageId": "C0301",
            "confidence": "HIGH",
            "module": "0123",
            "obj": "",
            "line": 1,
            "column": 0,
            "endLine": 1,
            "endColumn": 4,
            "path": "0123",
            "absolutePath": "0123"
        }
    ],
    "statistics": {
        "messageTypeCount": {
            "fatal": 0,
            "error": 0,
            "warning": 0,
            "refactor": 0,
            "convention": 1,
            "info": 0
        },
        "modulesLinted": 1,
        "score": 5.0
    }
}

与旧 json 报告器的差异

对照 pylint/reporters/json_reporter.py 中 JSONReporter 与 JSON2Reporter 两个类的实现,差异非常清晰:

  1. 顶层结构:json 直接输出一个扁平的消息数组;json2 将消息收纳到 "messages" 键下,并新增 "statistics" 统计块。
  2. 字段命名:message-id(kebab-case)在 json2 中改为遵循 camelCase 约定的 messageId(见 JSONMessage 的 TypedDict 定义)。
  3. 新增字段:每条消息增加 confidence(置信度,如 HIGH、INFERENCE,取自 CONFIDENCE_MAP)与 absolutePath(绝对路径);旧 json 的 path 字段被保留并新增了绝对路径版本。
  4. 统计信息:json2 的 serialize_stats()(pylint/reporters/json_reporter.py#L168-L196)输出:
    • messageTypeCount:fatal / error / warning / refactor / convention / info 六类消息计数;
    • modulesLinted:被检查的模块数量(len(stats.by_module));
    • score:依据 evaluation 配置项计算出的评分。

其中 score 的算法在源码中直接调用 evaluation 表达式(默认表达式见 pylint/lint/base_options.py 的 evaluation 选项定义),可用变量包括 fatal、error、warning、refactor、convention、info 以及 statement。

# 也可以将 json2 输出直接写入文件
pylint -f json2 my_module.py > pylint_report.json

核心亮点二:pyreverse 图表的全面增强

3.0 对 pyreverse(Pylint 内置的 UML 图生成工具)进行了多项重要改进,相关选项全部定义在 pylint/pyreverse/main.py。

新的默认配色(对色盲更友好)

pyreverse 采用了一套新的默认调色板,取自 Paul Tol 的色觉友好配色方案,定义在 pylint/pyreverse/main.py#L35-L46 的 DEFAULT_COLOR_PALETTE:

DEFAULT_COLOR_PALETTE = (
    "#77AADD",  # light blue
    "#99DDFF",  # light cyan
    "#44BB99",  # mint
    "#BBCC33",  # pear
    "#AAAA00",  # olive
    "#EEDD88",  # light yellow
    "#EE8866",  # orange
    "#FFAABB",  # pink
    "#DDDDDD",  # pale grey
)

如果你喜欢其他颜色,可以通过新增的 --color-palette 选项指定自定义颜色列表:

pyreverse --color-palette="#112233,#445566,#778899" my_package

该选项类型为 csv,与 --max-color-depth(按包深度着色)配合使用。

新选项 --show-stdlib / -L

标准库模块默认不再出现在图中(与 --show-builtin 的行为类似),需要通过新选项显式包含:

pyreverse -L my_package     # 包含标准库模块
pyreverse --show-stdlib my_package

新选项 --no-standalone

用于从图中排除没有关联关系的孤立节点,只显示有连接关系的节点,让大项目图更聚焦:

pyreverse --no-standalone my_package

源码中该选项的帮助文本为 "Only show nodes with connections."(pylint/pyreverse/main.py#L195-L202)。

包依赖图中标注仅用于类型检查的导入

在 pyreverse 的包依赖图中,当某个模块只是出于类型检查目的(如 TYPE_CHECKING 块内)导入另一个模块时,会以特殊方式标注,便于区分运行时依赖与类型依赖。

输出统计信息与 --verbose

pyreverse 现在会在运行时打印包统计信息;新增的 --verbose 标志用于输出包含已解析模块的原始日志(类似旧版本输出),调试时使用:

pyreverse --verbose my_package

移除的内容

  • .vcg 输出格式(Visualization of Compiler Graphs)被移除,直接支持的格式收缩为 dot、puml/plantuml、mmd、html(见 pylint/pyreverse/main.py#L27-L33 的 DIRECTLY_SUPPORTED_FORMATS);其他格式会尝试调用 Graphviz 的 dot 工具生成。
  • __implements__ 机制被完全移除:它基于 2001 年提出、2006 年被否决的 PEP 245,pyreverse 也不再考虑 __implements__ 生成图。

破坏性变更:必须关注的迁移点

1. 消息启停的顺序语义变化

在 3.0 中,对单个消息的 --enable / --disable 即使在同一次配置中后面跟着 --enable=all 或 --disable=all,也会生效。例如:

pylint my_module --enable=fixme --disable=all

以前 --disable=all 会覆盖前面的 --enable=fixme,现在 fixme 消息会被正常发出。若要恢复旧行为,删除多余的靠前选项即可。相关启停逻辑在 pylint/lint/message_state_handler.py 中实现,其中 _msgs_state 维护每个消息的启用状态,disable 对 "all" 的处理会保留 Pylint 自身必须开启的消息(见 pylint/lint/message_state_handler.py#L91-L97)。

2. Python 3.7 运行支持移除

不再支持用 Python 3.7 启动 Pylint(即运行环境至少为 Python 3.8)。但被检查的代码如果基于 3.7 编写,仍可通过 --py-version=3.7 指定目标版本继续检查:

pylint --py-version=3.7 legacy_code.py

3. try 块中的禁用注释现在作用于 except 块

try:
    # pylint: disable=some-message
    risky()
except Exception:
    risky_again()   # 3.0 起这里的 some-message 也会被禁用

此前只有存在 else 子句时才会生效,3.0 统一了这一行为。

4. pylint.config.PYLINTRC 移除

经过弃用期后,pylint.config.PYLINTRC 属性被删除,请改用 pylint.config.find_default_config_files 生成器。这一用法在 pylint/lint/run.py#L166 中可以看到实际应用:

default_file = next(config.find_default_config_files(), None)

5. MASTER / master 配置节不再允许

在 setup.cfg 或 tox.ini 中,配置节必须以下划线约定的工具名前缀开头。旧的 [MASTER] / [master] 写法不再合法,请改为:

[pylint.main]
max-line-length=100

6. overgeneral-exceptions 只接受全限定名

overgeneral-exceptions 选项现在只识别全限定名称,例如 builtins.Exception 而不是 Exception。如果你覆盖过该选项,必须改用全限定名;3.0 中仍会发出警告,该警告将在 3.1.0 移除。

7. epylint 从仓库移除

epylint(Emacs 相关工具)已迁移到独立项目维护,不再随 Pylint 分发。

8. 配置解析彻底切换到 argparse

所有与 optparse 相关的配置解析代码被删除,配置加载统一由 pylint/config/config_file_parser.py 完成,支持 .pylintrc、pyproject.toml(TOML)、setup.cfg / tox.ini(INI)等多种格式。

需要用户操作的变更(一):invalid-name 与命名长度检查

invalid-name 消息此前默认会检查名称是否至少 3 个字符——这实际上把"大小写风格"和"名称长度"两件不相干的事混在了一起,用户经常对此感到意外。3.0 移除了这个默认长度限制。

如果你仍希望保留最短长度检查,官方给出两种恢复方式:

方式一:接受 disallowed-name 消息,配置正则匹配 1~2 个字符的名称(需同时启用 disallowed-name):

[pylint.main]
bad-names-rgxs="^..?$"
enable=disallowed-name

方式二:希望继续使用 invalid-name 消息,则通过细粒度的正则选项(如 variable-rgx、class-rgx、function-rgx 等)自行控制。旧版正则可在移除长度要求的 PR 中查找。

从源码看,pylint/checkers/base/name_checker/checker.py#L279-L287 中 bad-names-rgxs 的默认值已为空字符串 "",且 disallowed-name 与 invalid-name 是两个独立消息(见 checker.py#L215)。同时 3.0 放宽了 typealias 名称的整数限制(Good2Name、GoodName2 合法,_1BadName 仍非法),且 Union 不再被视为命名检查意义上的类型别名。

需要用户操作的变更(二):隐式布尔检查合并

pylint.extensions.emptystring(比较空字符串)与 pylint.extensions.compare-to-zero(比较零)两个扩展被移除,其检查能力并入隐式布尔检查器(ImplicitBooleanessChecker):

  • compare-to-zero 更名为 use-implicit-booleaness-not-comparison-to-zero;
  • compare-to-empty-string 更名为 use-implicit-booleaness-not-comparison-to-string;
  • 两个新消息默认关闭,需要显式 --enable;
  • 旧扩展必须从 load-plugins 选项中移除,否则会报错。

从 pylint/checkers/refactoring/implicit_booleaness_checker.py 的源码可以确认:消息 C1804(字符串版本)与 C1805 级别的零比较检查均设置了 "default_enabled": False(见 implicit_booleaness_checker.py#L81-L90),且旧消息名如 len-as-condition 保留了 old_names 映射。这些检查共享大部分触发条件,因此也更易维护、性能更好,同时消息文本变得更明确、更可操作。

迁移示例:

# 旧写法(3.0 中失效)
load-plugins=pylint.extensions.emptystring,pylint.extensions.compare-to-zero

# 新写法
enable=use-implicit-booleaness-not-comparison-to-zero,use-implicit-booleaness-not-comparison-to-string

新增检查项(New Checks)

3.0 新增了三个内置检查和一个扩展检查,可在 doc/data/messages 目录下找到各自的文档化说明:

invalid-field-call(E3701)

新增独立的 DataclassChecker 模块(pylint/checkers/dataclass_checker.py),检查 dataclasses.field() 的非法用法——它只能作为 dataclass 类体内的赋值值,或出现在 make_dataclass() 函数中。源码中的检测逻辑通过 safe_infer 确认 field 确实来自 dataclasses 模块(_is_dataclasses_module),再检查其作用域是否为 dataclass 类或 make_dataclass 调用。

from dataclasses import dataclass, field

@dataclass
class Good:
    x: int = field(default=0)   # 合法

y = field(default=0)            # E3701: 非法使用 field()

return-in-finally

在 finally 子句中检测到 return 语句时发出警告(pylint/checkers/base/basic_checker.py#L264),因为 finally 中的 return 会覆盖 try 块的返回值,通常是隐藏 bug。

kwarg-superseded-by-positional-arg

当函数同时具有位置限定参数(positional-only)和关键字可变参数字典(**kwargs),调用时用关键字实参传入与位置限定参数同名的参数时发出警告——该关键字会被并入 **kwargs,行为可能与直觉相悖(pylint/checkers/typecheck.py#L416)。

扩展:prefer-typing-namedtuple(R6105)

CodeStyleChecker 新增消息,建议在 Python 3.6+ 上将 collections.namedtuple 调用改写为继承 typing.NamedTuple 的类(pylint/extensions/code_style.py#L67-L77)。需要加载扩展并显式启用:

load-plugins=pylint.extensions.code_style
enable=prefer-typing-namedtuple
# 不推荐
Point = collections.namedtuple("Point", ["x", "y"])

# 推荐
class Point(typing.NamedTuple):
    x: int
    y: int

误报修复(False Positives Fixed)精选

3.0.0 共修复了 24 处误报,3.0.1~3.0.4 又追加修复 12 处,按主题归纳如下:

变量作用域与赋值相关

  • used-before-assignment:循环中使用名称且依赖 except 块中早期赋值 + else: continue 时不再误报(#6804);finally 块中 try 与各异常处理都有赋值时不再误报(#9451);用法与赋值被同一条件在不同语句中保护时不再误报(#8167);TYPE_CHECKING 导入用于多个作用域时修复漏报(#8198)。
  • unused-argument:__new__ 未使用 __init__ 的全部参数时不再误报(#3670)。
  • undefined-variable / unused-argument:Python 3.12 泛型类型语法(PEP 695)下不再误报(#9193)。

类型与注解相关

  • no-member:装饰器提供的函数属性不再误报(#9246)。
  • inherit-non-class:泛型 Protocol 不再误报(#9106)。
  • isinstance-second-argument-not-valid-type:联合类型包含 None 时不再误报(#8424)。
  • pointless-string-statement:Python 3.12 类型别名上的 docstring 不再误报(#9268)。
  • invalid-name:enum.Enum 中无赋值的类型注解类变量不再误报(#7402);typealias 名称允许数字(#8485)。

表达式与调用相关

  • unnecessary-lambda:调用带关键字参数而 lambda 不带时不再误报(#9148);lambda 在函数体中使用自身参数(非最终参数)时不再误报,如 lambda foo: (bar if foo else baz)(foo)(#8496)。
  • unnecessary-comprehension:浅拷贝建议修正——b = [x for x in a] 不应建议改写为 b = a(会改变引用语义),正确建议是 list(a)(#9172)。
  • nested-min-max:嵌套列表不再误报(#9307)。
  • invalid-exception-operation:拼接异常类型元组不再误报(#9288)。
  • logging-not-lazy:显式拼接的字符串参数不再误报(#8410)。
  • unused-import:six.with_metaclass 的用法不再误报(#7506);并开始遵守 dummy-variables-rgx 选项(#8500)。
  • arguments-differ:重写 __init_subclass__ 不再误报(#8919);positional-only-arguments-expected(#8555)与 keyword-arg-before-vararg(#8570)的位置参数默认值场景均修复。
  • no-value-for-parameter:类体内调用 staticmethod 不再误报(#9036)。

统计与循环相关

  • too-many-try-statements、too-complex、too-many-branches:正确统计 try 下的语句数(#7767)。
  • unbalanced-dict-unpacking:改为检查每个待解包值的长度与解包目标数是否匹配,而非循环迭代值数量(#8156)。

框架适配

  • 将 asyncSetUp 加入默认的 defining-attr-methods 列表,消除 unittest.IsolatedAsyncioTestCase 下的 attribute-defined-outside-init 警告(#8403)。

漏报修复(False Negatives Fixed)

  • 模块级函数在定义前被调用时,现在会发出 used-before-assignment(#1144)。
  • infer_kwarg_from_call() 应用于更多检查,主要解决各类漏报(#7761)。
  • 函数内定义的 TypeAlias 变量现在会被 invalid-name 检查(#8536)。
  • 同时包含位置限定参数 name 与 **kwargs 的函数,以关键字实参传入 name 时补发 no-value-for-parameter(#8559)。
  • too-many-arguments 现在计入 positional-only 与 keyword-only 参数(#8667)。
  • 内置方法如 dict.update() 的调用现在发出 assignment-from-no-return;list.sort() 为保持一致改为发出 assignment-from-no-return 而非 assignment-from-none(#8714、#8810)。
  • consider-using-augmented-assign 扩展到字典和列表(#8959)。

其他 Bug 修复要点

并行与插件(--jobs)

配置与推断

  • 解析配置中的正则列表时,忽略花括号内的逗号(正则量词,如 {2,3}),避免误分割(#7229)。
  • ignored-modules 现在对 no-name-in-module 生效(#7578)。
  • sys.argv 始终视为无法推断的值,不再使用传给 Pylint 的实际参数(#7710)。
  • preferred-modules 修复部分匹配回归,改为完整匹配(#8453)。
  • 修复 tomlkit v0.11.8 导致生成配置文件时的崩溃:Can't add a table to a dotted key(#8632)。

pyreverse 图表质量

  • 类字段、箭头、类型注解不再重复显示(#8189、#8522、#8888)。
  • 输出文件名含 / 时不再崩溃(#8504)。
  • dot 输出的换行错误修复(#8671)。
  • 不再将 Optional 添加进含 None 的 | 注解(#9014)。
  • 多个类关联箭头只显示最强的一个(#9045)。

其他

  • --clear-cache-post-run 现在也清理持有 AST 节点引用的 LRU 缓存(#8361)。
  • 未导入就使用 TYPE_CHECKING 时不再崩溃(#8434);TYPE_CHECKING else-if 分支下的导入不再触发 used-before-assignment 误报(#8437)。
  • method-hidden 在使用 cached_property 装饰器时不再误报(#8753)。
  • 修复 BaseChecker 哈希不一致导致部分报告未导出的问题(#9001)。
  • 修复 linter 自身相关消息(如 unknown-option-value)导致的统计数据丢失,从而影响 score、fail-on 等(#9059)。
  • 修复 for 循环中一元操作数导致的 refactoring checker 崩溃(#9074)。
  • consider-using-generator 对带 default 关键字的 min() 输出更准确(#8563)。
  • docparams 扩展把类型注释视为类型文档(#6287)。

内部变更与 API 清理(面向插件/二次开发)

3.0 是一次彻底的 API 收口,对编写自定义检查器或报告器的开发者影响重大:

  • get_message_definition 从 base checker API 移除,请通过 MessageStore 访问消息定义。
  • pylint.interfaces 中继承 Interface 的类全部移除;Checker 只应继承 BaseChecker 等 pylint.checkers 中的类型,Reporter 只应继承 BaseReporter。
  • FileState 现在必须传入 modname 与 msg_store;collect_block_lines 移除;Pylinter.current_name 不再允许为 null。
  • Reporter.set_output 移除,改用 reporter.out = stream。
  • 一批旧工具函数移除,官方给出替代方案:
    • MapReduceMixin → 自行实现 get_map_data / reduce_map_data;
    • is_inside_lambda → utils.get_node_first_ancestor_of_type(x, nodes.Lambda);
    • check_messages → utils.only_required_for_messages;
    • is_class_subscriptable_pep585_with_postponed_evaluation_enabled → is_postponed_evaluation_enabled + is_node_in_type_annotation_context;
    • get_python_path → discover_package_path(Python 3.3 起不再保证存在 __init__.py,尤其 PEP 420 场景);
    • fix_import_path → augmented_sys_path;
    • get_global_option → checker.linter.config。
  • colorize_ansi 只接受 MessageStyle 对象;ColorizedTextReporter 只接受 ColorMappingDict。
  • Pylinter.check 只接受字符串序列,不再接受单个字符串;check_single_file 改为 check_single_file_item。
  • Run 类(及 testutils 的 _Run)的 do_exit 参数移除。
  • MessageDefinition.may_be_emitted 的 py_version 参数变为必填,或用 MessageDefinition.is_message_enabled 替代。
  • Message 的 location 参数必须是 MessageLocationTuple;OutputLine 参数数量必须精确(功能测试输出可用工具自动重新生成)。
  • pylint.utils 中的 is_typing_guard、is_node_in_typing_guarded_import_block、is_node_in_guarded_import_block 移除,改用 is_sys_guard 与 in_type_checking_block 组合。

其他变更

  • 类型注解公开:Pylint 现在暴露自身的类型注解(py.typed),方便类型检查器使用。
  • pyproject.toml 递归查找:配置查找会沿父目录一直向上到项目根或文件系统根。
  • 消息重命名:unneeded-not 更名为 unnecessary-negation(旧名作为别名保留,见 pylint/checkers/refactoring/not_checker.py#L22-L25 的 old_names)。
  • 仓库根目录新增 CITATION.cff 引用元数据。

性能改进

  • 得益于 astroid 对推断缓存的更好利用,Pylint 运行速度提升(至少约 5%)。
  • 优化 is_trailing_comma(),并缓存 class_is_abstract()。
  • 当所有消息都被禁用时立即退出,避免无谓的完整扫描。

升级迁移清单

从 2.x 升级到 3.x 时,建议按以下清单逐项核对:

  1. 运行环境:确保 Python ≥ 3.8;被检查代码若基于 3.7 使用 --py-version=3.7。
  2. 消息启停:检查配置中 enable=... 与 disable=all 的先后顺序是否符合新语义。
  3. load-plugins:移除 pylint.extensions.emptystring 与 pylint.extensions.compare-to-zero;如需对应检查,显式 enable 新消息名。
  4. invalid-name:如需保留最短 3 字符规则,配置 bad-names-rgxs="^..?$" 并启用 disallowed-name,或自定义各命名正则。
  5. 配置文件节:setup.cfg / tox.ini 中将 [MASTER] 改为 [pylint.main]。
  6. overgeneral-exceptions:改为全限定名(如 builtins.Exception)。
  7. 自定义代码:对照上文"内部变更"列表替换已移除的 API。
  8. 输出解析:若依赖 JSON 输出,建议切换为 json2 并适配 messages / messageId / statistics 结构。

Pylint 3.0 在维持检查能力的同时,完成了运行环境、依赖、输出格式与内部 API 的现代化。对于普通用户,json2 与 pyreverse 增强是立即可用的收益;对于插件开发者,则需要按上述内部变更清单完成适配。更多细节可继续阅读仓库中的 完整变更记录说明 与各消息的文档化条目(doc/data/messages)。

登录后查看全文
pylint