What's New in Pylint 4.1

:Release:4.1 :Date: TBA

Summary -- Release highlights

The duplicate-code checker and symilar received optimizations that result in considerable performance improvements and memory use reduction on larger codebases. For example, pandas analysis went from 20 min to 55 s and pylint does not get OOM-killed when analyzing cpython anymore.

The required astroid version is now 4.1.1. See the astroid changelog for additional fixes, features, and performance improvements applicable to pylint.

What's new in Pylint 4.0.0-dev0?

Release date: TBA

Breaking Changes

  • The confidence parameter is no longer nullable on any APIs except for is_message_enabled (where confidence=None means "don't filter by confidence"). The default became interfaces.UNDEFINED (an immutable value); the behavior is unchanged unless you were passing an explicit None. This avoids a runtime check in multiple function to set the default value conditionally.

    The constants.MSG_STATE_* integer were replaced by a MessageDisableReason enum. The old names remain as deprecated aliases pointing at the enum members. MessageDisableReason is an IntEnum so existing code comparing the return of _get_message_state_scope to the literal 0 / 1 / 2 keeps working.

    Refs #11018 (#11018)

New Features

  • Add support for ignore-pattern-in-long-lines to allow ignoring specific parts of a line when checking line length.

    Refs #3352 (#3352)

  • The dict-init-mutate message now includes a suggested dictionary literal showing how to combine the initialization and subsequent mutations into a single statement.

    Closes #7819 (#7819)

  • pyreverse: add --no-signatures to show method names without parameter lists or return type annotations in class diagrams.

    Closes #10772 (#10772)

  • Add support for --known-first-party similar to --known-third-party.

    Refs #10803 (#10803)

False Positives Fixed

  • Fix a false positive for too-many-arguments in (non-static) methods and classmethods.

    Closes #8675 (#8675)

  • Fix a false positive for inconsistent-return-statements when an instance method annotated with NoReturn (or Never) is called via the class rather than an instance (e.g. MyClass.raise_method(obj)). The unbound method form is now recognised as never returning, matching the existing behaviour for the bound-method form.

    Closes #9692 (#9692)

  • Fix a false positive for no-member when a value is inferred to several possible types and at least one of them defines a dynamic __getattr__.

    Closes #9833 (#9833)

  • Fix used-before-assignment false positive for names bound in only some arms of an if/elif/else chain.

    Closes #9879 (#9879)

  • Fix a false positive for function-redefined (E0102) when reusing names that match dummy-variables-rgx (such as _), which is common for pytest-bdd step definitions. This restores the behavior from before pylint 4.0.0; as a consequence the false negative fixed in #9894 is reintroduced for functions whose name matches dummy-variables-rgx.

    Closes #10665 (#10665)

  • Fix a false positive for unexpected-keyword-arg for dataclasses using generic type aliases (PEP 695).

    Closes #10703 (#10703)

  • Fix possibly-used-before-assignment false positive when using self.fail() in tests.

    Closes #10743 (#10743)

  • Fixed false positive for logging-unsupported-format when no arguments are provided to logging functions.

    According to Python's logging documentation, no formatting is performed when no arguments are supplied, so strings like logging.error("%test") are valid.

    Closes #10752 (#10752)

  • Fix false positive unreachable when calling a function with @overload where one signature returns NoReturn.

    Closes #10785 (#10785)

  • Fix a false positive for too-many-function-args for dataclasses using generic type aliases (PEP 695).

    Closes #10788 (#10788)

  • Fix a false positive for invalid-name where a dataclass field typed with Final was evaluated against the class_const regex instead of the class_attribute regex.

    Closes #10790 (#10790)

  • Avoid emitting unspecified-encoding (W1514) when py-version is 3.15+.

    Refs #10791 (#10791)

  • Fix a false positive relative-beyond-top-level error when linting specific files in namespace packages in parallel mode by augmenting sys.path before loading plugins and expanding files consistently for parallel workers.

    Closes #10794 (#10794)

  • Fix undefined-variable false positive when a name used as a metaclass argument in a nested class is referenced again later in the module.

    Closes #10823 (#10823)

  • Fix a false positive for unused-variable where global variables matching dummy-variables-rgx were still reported as unused when allow-global-unused-variables was disabled.

    Closes #10890 (#10890)

  • Fix # pylint: enable inside a try block leaking into the except handler. For example in the following code, no-member is no longer incorrectly re-enabled in the except block:

    class Basket:
        # pylint: disable=no-member
        def pick(self):
            try:
                # pylint: enable=no-member
                print(self.apple)  # no-member emitted here
            except KeyError:
                print(self.banana)  # no-member NOT emitted here (correct)
    

    Requires astroid 4.2.

    Refs #10933 (#10933)

  • Fix a false positive for bad-dunder-name when there is a user-defined __suppress_context__ attribute on exception subclasses.

    Closes #10960 (#10960)

  • Fix used-before-assignment false positive for names bound by from X import * in every branch of an if/elif/else chain.

    Refs #10980 (#10980)

  • Fix access-member-before-definition false positive for bare type annotations (self.x: Type) that don't assign a value.

    Refs #11015 (#11015)

  • Check for metaclass __call__ signature when evaluating arguments of a class call.

    Closes #11032 (#11032)

  • Fix false positive method-hidden when cached_property is imported directly with from functools import cached_property.

    Refs #11037 (#11037)

False Negatives Fixed

  • missing-param-doc and missing-type-doc no longer false-negative on NumPy-style parameters whose type line includes a default value, e.g. number : int, default 0. Any text after the colon on the type line is now accepted as the type, matching the NumPy style guide.

    Closes #6211 (#6211)

  • Fix a false negative for abstract-method where a concrete subclass inheriting from an abstract class (without redeclaring abc.ABC or ABCMeta) was treated as abstract and silently exempted from the check. A class is now only considered abstract when it opts in explicitly, via direct abc.ABC inheritance, metaclass=ABCMeta, an @abstractmethod defined on the class, or being a Protocol.

    Closes #7950 (#7950)

Other Bug Fixes

  • dangerous-default-value now detects mutable default values in typing.NamedTuple field definitions.

    Closes #3716 (#3716)

  • Fix enabling checks from extensions which are disabled by default if multiple jobs are used.

    Closes #10037 (#10037)

  • Fix a crash in consider-using-enumerate when the for loop target is an attribute (e.g. for self.idx in range(len(x))) rather than a simple variable name.

    Closes #10099 (#10099)

  • wrong-import-position now exempts try, if, with, and match blocks from marking the import boundary. Fixed async def not being detected as an import boundary. Pragma on non-import lines now suppresses following imports until the next non-import.

    Closes #10589 (#10589)

  • Fix duplicate messages for extension checks if multiple jobs are used.

    Refs #10642 (#10642)

  • Fix --known_third_party config being ignored.

    Closes #10801 (#10801)

  • Fixed dynamic color mapping for "fail-on" messages when using multiple reporter/output formats.

    Closes #10825 (#10825)

  • dependency on isort is now set to <9, permitting to use isort 8.

    Closes #10857 (#10857)

  • Fix crash when checking attribute-defined-outside-init on classes that inherit from a base class pylint cannot fully analyze.

    Closes #10892 (#10892)

  • Fix an issue where discovery can miss a similarly named directory if a shorter named directory is processed first.

    Closes #10969 (#10969)

  • Fixed inflated message occurrence counts in the final Messages report when running pylint in parallel mode with --jobs greater than 1.

    Closes #10996 (#10996)

  • Fix add_message silently overwriting an explicit col_offset=0 (or any other zero-valued line/end_lineno/end_col_offset) with the AST node's value. The internal _add_one_message helper used a falsey check (if not col_offset:) to detect an omitted argument, which incorrectly treated a legitimate 0 the same as None. It now uses an identity check against None.

    Refs #11020 (#11020)

  • Fix a crash in the name checker when a non-constant value is passed as the covariant or contravariant argument of a TypeVar.

    Closes #11022 (#11022)

  • Fix a crash in the variable checker when a name resolves to a dataclass-synthesized __init__, which has no line number.

    Closes #11023 (#11023)

  • Fix a crash in the variables checker when NotImplemented is used as the test of an if statement, which raised a TypeError in a boolean context on Python 3.14.

    Closes #11025 (#11025)

  • Avoided a crash from the implicit booleaness checker for len() calls without arguments.

    Closes #11028 (#11028)

  • Fix a crash in the variables checker when a class declares a metaclass whose attribute-access chain does not bottom out at a name (e.g. class C(metaclass=None._)). Originally reported as pylint-dev/astroid#3066.

    Refs #11031 (#11031)

  • Fix a crash in the name checker when a chained assignment of a TypeAlias value has a non-name target such as a Subscript (for example a[0] = b = TypeAlias).

    Closes #11056 (#11056)

  • Fix a crash in the deprecated checker when __import__ is called with a non-string constant argument (for example __import__(1)).

    Closes #11059 (#11059)

  • Prevent a crash in unexpected-keyword-arg analysis when infer_call_result() raises InferenceError while inspecting decorator return signatures.

    Closes #11070 (#11070)

  • Fix a crash in the typecheck checker when a class uses a non-class object (for example a function) as its metaclass= argument.

    Closes #11071 (#11071)

Other Changes

  • You can now set the files option in configuration files and on the command line. Passing files without the --files flag is still supported. This allows to set files to files = my_source_directory and invoking pylint with only the pylint command similar to how other CLI tools allow to do so. The help message can always be invoked with pylint -h or pylint --help.

    Closes #5701 (#5701)

  • Document that the wrong-import-order (C0411) classification of imports as third-party vs first-party depends on the current working directory and recommend known-first-party as the deterministic workaround.

    Closes #8801 (#8801)

Internal Changes

  • Add assertDoesNotAddMessages to CheckerTestCase to assert that specific messages are not emitted, while allowing other messages to be present. This complements assertNoMessages which asserts that no messages at all are emitted.

    Refs #9598 (#9598)

Performance Improvements

  • Lazily import isort, dill, multiprocessing/concurrent.futures, and tomlkit so they are only loaded when actually needed. This reduces startup time by ~25% (e.g. --version: 91 => 67 ms, --help: 176 => 133 ms, single-file lint: 272 => 226 ms).

    Closes #2866 (#2866)

  • The duplicate-code checker no longer runs when its message (R0801) is disabled, even if reports=yes is set. Previously, the checker's report (RP0801) would cause the expensive similarity computation to run regardless.

    Closes #3443 (#3443)

  • Sped up the duplicate-code checker. When run inside pylint the checker now reuses the already-parsed AST instead of re-parsing every file like it has to do when launched via symilar, and it uses a rolling hash window with caching across file pairs. Additionally, a quadratic blow-up in the hash-matching phase is avoided by switching algorithm at a threshold, which previously caused the checker to hang on files with many repeated lines.

    Speedup scales with codebase size from 1.5x on small projects (~10k lines), to 20x on large ones (500k+ lines). Memory usage also drops 12-27%. Codebases that previously hung or were OOM-killed could now complete.

    Refs #10881 (#10881)

  • Skip isort classification in the import checker when no import-ordering message is enabled, and cache the isort configuration so it is built once instead of once per import statement. Skipping the isort processing become a negligible improvement once the caching is applied. pylint became ~17% faster on ansible (~=4500 imports) even with isort enabled.

    Refs #10886, #2866, #10637 (#10886)