What's new in Pylint 4.1.0?
Release date: 2026-09-29
Startup is about 25% faster thanks to lazy imports. The import checker caches
its isort configuration, which makes pylint about 17% faster on ansible.
Finding the files to lint with --recursive=y no longer walks ignored
directories such as .venv or node_modules, which took seconds on large
trees. The duplicate-code checker and symilar also 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.
Python 3.15 support progresses: the unpacking in comprehensions added by
PEP 798 no longer raises false positives, and the standard library
deprecations of Python 3.15 are followed.
For CI, there is a new built-in junit output format
(--output-format=junit), the NO_COLOR and FORCE_COLOR environment
variables are respected, and the files to lint can now be set with the
files option in the configuration file.
New checks: looping-through-iterator, impossible-comparison,
chained-comparison-all-equal and
using-comprehension-unpacking-in-unsupported-version.
Running with --jobs no longer duplicates the messages of extensions or
ignores extensions enabled in the configuration.
Plugin authors: the confidence parameter can no longer be None, except
in is_message_enabled, and the MSG_STATE_* constants are deprecated in
favor of the MessageDisableReason enum.
The required astroid version is now 4.3.2. See the astroid changelog for additional fixes, features, and performance improvements applicable to pylint.
Breaking Changes
-
The
confidenceparameter is no longer nullable on any APIs except for
is_message_enabled(whereconfidence=Nonemeans "don't filter by
confidence"). The default becameinterfaces.UNDEFINED(an immutable value);
the behavior is unchanged unless you were passing an explicitNone. This
avoids a runtime check in multiple function to set the default value conditionally.The
constants.MSG_STATE_*integer were replaced by aMessageDisableReasonenum.
The old names remain as deprecated aliases pointing at the enum members.
MessageDisableReasonis anIntEnumso existing code comparing the return of
_get_message_state_scopeto the literal0/1/2keeps working.Refs #11018
New Features
-
Add support for
ignore-pattern-in-long-linesto allow ignoring specific parts of a line when checking line length.Refs #3352
-
Support for the
NO_COLORandFORCE_COLORenvironment variables has been added.
When running pylint, the reporter that writes to stdout is switched betweentext
andcolorizedaccording to the requested mode.
The order is:NO_COLOR>FORCE_COLOR>--output-format=....Closes #3995
-
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
-
Add a built-in
junitoutput format (--output-format=junit) that produces JUnit-compatible XML output for CI/CD integration with Jenkins, Azure DevOps, GitLab CI, and GitHub Actions.Closes #9143
-
Trailing pragmas understood by other common tooling (
# type: ignore,
# pyright: ignore,# noqa,# pragma: no coverand
# pragma: no branch) are no longer counted toward the line length, so a line
is not flagged asline-too-longsolely because of such a pragma. This mirrors
the existing behaviour for Pylint's own# pylint:pragmas.Closes #10172
-
pyreverse: add
--no-signaturesto show method names without parameter lists or return type annotations in class diagrams.Closes #10772
-
Add support for
--known-first-partysimilar to--known-third-party.Refs #10803
New Checks
-
Added a new checker
looping-through-iterator(W4801) to detect when an iterator from an outer scope is consumed in a nested loop, which can lead to the iterator being unexpectedly exhausted.Refs #2996
-
Add new checks
impossible-comparisonandchained-comparison-all-equal.
impossible-comparisonflags boolean conditions whose chain of numeric
comparisons is logically contradictory and can never be true (for example
a > b and b > a).chained-comparison-all-equalflags boolean conditions
whose operands form a cycle of weak inequalities (<=or>=) and can be
simplified to a chain of equalities (for examplea >= b and b >= ais
equivalent toa == b).Closes #5814
-
Add
using-comprehension-unpacking-in-unsupported-version(W2607), emitted when
the code uses the unpacking in comprehensions added by PEP 798 while
py-versionstill includes a Python version that cannot compile it.Refs #10982
False Positives Fixed
-
Fix a false positive for
unnecessary-negation(C0117) when negating a
comparison between dict views (dict.keys()/dict.items()), which like
set/frozensetsupport only a partial (subset/superset) ordering, so
not a.items() <= b.items()is not equivalent toa.items() > b.items().Closes #3668
-
Fix false positives for
attribute-defined-outside-initwhere__init__
(etc.) uses a helper method to create attributes.Closes #5214
-
Fix a false positive for
consider-using-generatoranduse-a-generator
with an asynchronous list comprehension. Turning it into a generator expression
would create an asynchronous generator, which those functions cannot consume.Closes #7271
-
Fixed a false positive
assigning-non-slotwhen assigning to an inherited
descriptor through an instance returned by a method annotated with the
subclass. This regressed in pylint 3.0.0.Fixed by upgrading astroid to 4.1.0.
Closes #8053
-
Fixed a false positive
no-memberwhen aclassmethodannotated with
typing.Selfis overridden in a subclass and its result is used directly,
as inSubclass.build().only_on_subclass(). The return type is now narrowed
to the subclass rather than the base class. This regressed in pylint 3.0.0.Fixed by upgrading astroid to 4.1.0.
Closes #9159
-
Avoid emitting
deprecated-classfor imports inside a recognized
sys.version_infoguard.Closes #9533
-
Fix a false positive for
inconsistent-return-statementswhen an instance
method annotated withNoReturn(orNever) 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
-
Fix
used-before-assignmentfalse positive for names bound in only some arms of anif/elif/elsechain.Closes #9879
-
Fix a false positive for
useless-parent-delegationwhen a method overrides a
method of a C-level parent whose signature cannot be inspected, such as
Exception.__init__. BecauseException.__init__accepts*args, an
override taking onlyselfnarrows the accepted arguments and is not useless.
Overrides ofobject.__init__are still reported.Closes #9994
-
Fixed a false positive
no-name-in-modulewhen a module is imported with
an alias that shadows its base module and a function namedformatis
called on the alias.Fixed by upgrading astroid to 4.3.1.
Closes #10193
-
Fixed a false positive
unsubscriptable-objecton instances of a generic class defining__class_getitem__.Closes #10360
-
Fixed a false positive
unexpected-keyword-argumentwhen passingdtype
tonumpy.concatenate().Fixed by upgrading astroid to 4.3.1.
Closes #10548
-
Fix a false positive for
unexpected-keyword-argfor dataclasses
using generic type aliases (PEP 695).Closes #10703
-
Fix false positive
unreachablewhen calling a function with@overloadwhere one signature returnsNoReturn.Closes #10785
-
Fix a false positive for
too-many-function-argsfor dataclasses
using generic type aliases (PEP 695).Closes #10788
-
Fix a false positive
relative-beyond-top-levelerror when linting specific files in namespace packages in parallel mode by augmentingsys.pathbefore loading plugins and expanding files consistently for parallel workers.Closes #10794
-
Fix
# pylint: enableinside atryblock leaking into theexcept
handler. For example in the following code,no-memberis no longer
incorrectly re-enabled in theexceptblock: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
-
Fix false positives for the Python 3.15 syntax added by PEP 798, unpacking in
comprehensions:star-needs-assignment-target(E0114) was emitted for the unpacked element
of a comprehension, e.g.[*sub for sub in lists].consider-using-dict-comprehension(R1717) was emitted for
dict([*pairs for pairs in nested]), which flattens its argument and is
therefore not equivalent to a key/value dict comprehension.
Refs #10982
-
Fix
access-member-before-definitionfalse positive for bare type annotations
(self.x: Type) that don't assign a value.Refs #11015
-
Fix a false positive for
assignment-from-no-returnwhen the called function's
body ends in an unconditionalraise, such aspathlib.Path.readlink()on
platforms without symlink support.Closes #11114
-
Fix a false positive for
protected-accesswhen a protected member is
accessed throughself.__class__, which is now treated liketype(self).Closes #11160
-
Treat
typing.NoReturnandtyping.Neverthe same asNoReturn/Neverwhen deciding that a call never returns.Closes #11271
-
Fix a false positive for
unreachableon the statement following an
instantiation of_sitebuiltins.Quitter(the class of theexitand
quitbuiltins). Only calling the instance terminates, not creating it.Closes #11310
-
Fixed a false positive
unbalanced-tuple-unpackingwhen unpacking theargs
of an exception.Fixed by upgrading astroid to 4.3.2.
Closes #11312
-
Fixed a false positive for
unnecessary-semicolonwhen an f-string ending in;is continued onto the next line with a backslash on Python 3.12+.Closes #11444
False Negatives Fixed
-
missing-param-docandmissing-type-docno 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
-
The
docparamsextension now emitsmultiple-constructor-docwhen
constructor parameters are documented in both the class docstring and the
constructor docstring, even when the constructor method is skipped by
no-docstring-rgx.Closes #6692
-
chained-comparisonis now emitted for additional simplifiable patterns
(e.g.a > 1 and a > 10) and its message now includes the suggested
simplification.Refs #7611
-
Fix a false negative for
abstract-methodwhere a concrete subclass
inheriting from an abstract class (without redeclaringabc.ABCor
ABCMeta) was treated as abstract and silently exempted from the check.
A class is now only considered abstract when it opts in explicitly, via
directabc.ABCinheritance,metaclass=ABCMeta, an
@abstractmethoddefined on the class, or being aProtocol.Closes #7950
-
no-value-for-parameteris now emitted when a call unpacks a dictionary literal
with**and that dictionary does not provide a required argument.Closes #8785
-
attribute-defined-outside-initnow reports attributes assigned with
setattr(self, "name", value)outside defining methods.
It no longer reports attributes assigned normally when a defining method of the
class or of a parent initializes them withsetattr.Closes #9798
-
superfluous-parens(C0325) no longer false-negatives on a single
parenthesised literal after theinkeyword, e.g.x in ("foo"). The
parentheses around a single string or number literal are now reported as
superfluous, while a tuple (x in ("foo",)) or a larger expression
(x in ("foo" + bar)) is still left untouched.Closes #9878
-
comparison-with-itselfnow detects repeated attribute chains such as
object.attribute == object.attribute.Closes #10713
-
not-an-iterableandnot-a-mappingare now also emitted for the value
unpacked by PEP 798 comprehension unpacking, e.g.[*number for number in numbers]or{**number for number in numbers}.Refs #10982
-
Fix a false negative in
unnecessary-negation(C0117):not (a is not b)andnot (a not in b)are now flagged (they simplify toa is banda in b), consistent with the existing handling ofis/in.Closes #11140
-
Emit
arguments-differwhen an overridden special method takes a different
number of parameters. Only renamed parameters and removed variadics stay
exempt, and the constructor family (__new__,__init__,
__init_subclass__and__post_init__) is still fully ignored.Closes #11295
-
access-member-before-definitionis now also emitted when__init__calls a
method that reads an instance attribute which__init__only assigns after the call.Closes #11338
-
Fix a false negative for
unspecified-encodingandbad-open-modewhen the
mode of anopencall is a parameter of the enclosing function that has a
default value. The default is now used to check the call, as a literal mode
would be. Calls with such a mode stopped being reported in pylint 4.0.8.Refs #11415
Other Bug Fixes
-
# pylint: disablecomments at the beginning of anelseblock (or on
the line just above theelsekeyword) now suppress messages in that block
instead of being ignored.Fixed by upgrading astroid to 4.3.1.
Closes #872
-
dangerous-default-valuenow detects mutable default values intyping.NamedTuplefield definitions.Closes #3716
-
Repeated
--output-formatoptions now write reports to every requested file instead of only the last one.Closes #8147
-
Fixed a crash when defining a functional
namedtuplewith a field name
that changes under NFKC normalization, like"µ"(MICRO SIGN).Fixed by upgrading astroid to 4.3.1.
Closes #8746
-
Fix a crash in
pyreversewhen a requested class cannot be inferred.Closes #9797
-
Fix enabling checks from extensions which are disabled by default if multiple jobs are used.
Closes #10037
-
Fixed an
AstroidBuildingErrorcrash when inheriting from a generic
dataclass that rebinds__init__in__init_subclass__.Fixed by upgrading astroid to 4.3.1.
Closes #10519
-
wrong-import-positionnow exemptstry,if,with, andmatchblocks from marking the import boundary. Fixedasync defnot being detected as an import boundary. Pragma on non-import lines now suppresses following imports until the next non-import.Closes #10589
-
Fix duplicate messages for extension checks if multiple jobs are used.
Refs #10642
-
Fix an issue where discovery can miss a similarly named directory if a shorter named directory is processed first.
Closes #10969
-
Follow the standard library deprecations of Python 3.15.
Refs #10982
-
Fixed inflated message occurrence counts in the final
Messagesreport when
running pylint in parallel mode with--jobsgreater than 1.Closes #10996
-
Fix a crash in
consider-using-dict-itemswhen theforloop or comprehension target is an attribute or a subscript (e.g.for self.key in d) rather than a simple variable name.Closes #11173
-
nan-comparisonnow also recognizesmath.nan,numpy.nan,Decimal("nan")
and any name or attribute that pylint can infer to a NaN constant, such as a module
level constant defined asmath.nan. Onlynumpy.NaN-- removed in numpy 2.0 --
andfloat("nan")were detected before. Infinities are still not reported, as
comparing against them is meaningful.Refs #11219
-
Fix a crash in the comparison checker when a NaN comparison operand is a call to a name that cannot be inferred, such as
1 == b('nan').Closes #11224
-
Fix a crash in
invalid-class-objectandassigning-non-slotwhen__class__is assigned outside a simple assignment (e.g.for obj.__class__ in classes:).Closes #11267
-
Avoid a fatal
astroid-errorininvalid-name,
stop-iteration-return,assigning-non-slotand
redefined-slots-in-subclassfor classes with duplicate or inconsistent
bases, which leave the class without an MRO to walk.Refs #11272
-
collections.abc.Callableandcollections.abc.Bufferno longer count towardstoo-many-ancestors. Every other abstract base class incollections.abcwas already ignored, so a class deriving fromCallablewas charged for an ancestor while an otherwise identical class deriving fromIterablewas not.Refs #11358
-
Fix
import-private-namedepending on the order of the checked files: type
annotations are now collected for each module instead of only for the first
module checked that contains an import. This removes a false positive on imports
used only as annotations and a false negative on private imports used at runtime.Closes #11466
Other Changes
-
Clarify how to choose the Python interpreter and
py-versionwhen linting a
project that supports multiple Python versions.Closes #5038
-
You can now set the
filesoption in configuration files and on the command line.
Passing files without the--filesflag is still supported. This allows to set
filestofiles = my_source_directoryand invokingpylintwith only
thepylintcommand similar to how other CLI tools allow to do so.
The help message can always be invoked withpylint -horpylint --help.Closes #5701
-
Removed messages (such as
print-statementorapply-builtin) now have
their own page in the documentation, with a link to the change that removed
them. They are also listed in the messages overview alongside renamed messages.Closes #6670
-
Documentation for options defined by
Run, such as--errors-onlyand
--init-hook, is now generated alongside checker configuration options.Closes #6938
-
Document that the
wrong-import-order(C0411) classification of imports as
third-party vs first-party depends on the current working directory and
recommendknown-first-partyas the deterministic workaround.Closes #8801
-
Clarify related
no-else-*messages so they say that only the firstelif
after the reported branch should change. Expand theno-else-return
documentation to explain later branches and when retaining anelifchain
can better communicate an exhaustive decision.Closes #9274
-
assignment-from-no-returnnow names the callable that does not return anything and,
for functions listed in the newknown-side-effects-only-functionsoption, hints at
the equivalent function to use instead (e.g.reversed(...)forreverse()).Closes #10383
Internal Changes
-
Add
assertDoesNotAddMessagestoCheckerTestCaseto assert that
specific messages are not emitted, while allowing other messages to be
present. This complementsassertNoMessageswhich asserts that no
messages at all are emitted.Refs #9598
-
The primer now pairs residual messages — first by
(symbol, path, obj)and then by
exact source location — and reports altered messages as a single changed entry with
a compact diff, rather than as a separate removal + addition. The location-based pass
also catches symbol renames at the same code position (e.g.used-before-assignment
→possibly-used-before-assignment). When several messages are eligible, the one
closest to the original line wins, so pairs never cross. New messages are classified
into fixed false positives (useless-suppression),astroid-errorfatal errors,
and the rest.astroid-errormessages are excluded from pairing (their text embeds
a unique crash-report path) so persistent crashes keep raising the prominent warning.
Truncated comments are now cut at a line break and keep their code fences and
<details>blocks closed.Refs #10914
-
The primer's project cache key is now derived from the commits pinned in
packages_to_prime.jsoninstead of the remote branch tips.mainand PR primer
runs now share the same project cache and lint files in the same on-disk order,
removing spurious diffs from primer comments (message positions and astroid inference
results depend on the order in which modules are linted).Closes #11192
Performance Improvements
-
Lazily import
isort,dill,multiprocessing/concurrent.futures,
andtomlkitso 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
-
Sped up the
duplicate-codechecker. 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 viasymilar, 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
-
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 became17% faster on ansible (=4500 imports) even with isort enabled. -
Finding the files to lint with
--recursive=yis faster. Directories matching
ignore,ignore-patternsorignore-paths(such as.venv,.git
ornode_modules) are no longer walked, and neither are the packages already
found. In a checkout of pylint with its virtual environment, this step is about
three times faster when.git,.toxand.venvare ignored, and about
twenty times faster when a large ignored tree is present.Directories and files are now also visited in sorted order, so the order in
which files are linted no longer depends on the file system. The order of
messages can change once for projects whose file system listed directories in
another order.Closes #11005