pylint review
Our Pylint 4.0.7 install gave us a typed, pure-Python command-line analyzer that infers what names can refer to through Astroid before reporting errors, suspicious behavior, design smells, and style violations. That inference finds problems in code with few type annotations, but it also makes a full run slower and more sensitive to dynamic framework behavior than syntax-only linters. Version 4.0.7 is a correction release: it fixes TypedDict instance names being treated as class names, stops several analyzer crashes, repairs unsafe `nested-min-max` suggestions, excludes PEP 695 type parameters from local-variable counts, and corrects several string-format and comparison findings.
Pylint 4.0.7 installed in 0.3 seconds, used 5 MB, and imported in 0.07 seconds in our sandbox with 0 audit findings. Use it for inference checks that faster syntax linters miss, but pin it and choose a deliberate message set instead of treating every default warning as policy.
We installed it
| Install | ✓ · 0.3s | 7 packages on disk · 5 MB |
| Import | ✓ | import pylint in 0.07s · pure Python · py.typed · requires Python >=3.10.0 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does pylint install cleanly?
Yes. In a fresh container with an empty cache, pip install pylint finished in 0.3s, leaving 7 packages and 5 MB on disk. pip-audit reported no known vulnerabilities.
What does pylint need to run?
Python >=3.10.0, and nothing compiled: it is pure Python. In our run import pylint succeeded in 0.07s, and the package ships py.typed for type checkers.
pylint or ruff: which should you use?
ruff: Choose it for very fast linting, broad rule coverage, formatting, and automatic fixes. Pylint 4.0.7 installed in 0.3 seconds, used 5 MB, and imported in 0.07 seconds in our sandbox with 0 audit findings.
When should you not use pylint?
Fast feedback is the priority: Pylint's README says inference makes it slower, while Ruff is built for the quick edit-and-fix loop
Use it if
- An untyped or partly typed Python codebase needs inference-based checks such as no-member, used-before-assignment, and wrong call arguments
- CI needs a bug-focused second pass after a faster formatter or linter
- Your organization wants custom Python checkers with named messages and configuration
- Class and package diagrams from the bundled `pyreverse` command would help document an inherited codebase
- Fast feedback is the priority: Pylint's README says inference makes it slower, while Ruff is built for the quick edit-and-fix loop
- The code relies heavily on generated attributes: Django, Pydantic, and similar frameworks can need a matching plugin to avoid false `no-member` reports
- You expect automatic repairs: Pylint mainly diagnoses and scores code, while Ruff and dedicated formatters can apply many edits
- No one will maintain the rule policy: default convention and refactor messages can overwhelm a legacy repository unless categories are introduced gradually
- GPL-2.0-or-later conflicts with embedding or distributing the analyzer in your product; running the separate CLI in development has a different compliance profile
- CI cannot pin the analyzer version: inference fixes routinely add or remove messages, and 4.0.7 alone changes naming, local-count, comparison, and formatting findings
Setup reality
Our clean pylint==4.0.7 install completed in 0.3 seconds. It left 7 packages occupying 5 MB, and pip-audit found 0 known vulnerabilities. The measured package data reports 13 direct dependencies, Python 3.10.0 or newer, pure Python code, and an unknown license value. It ships a py.typed marker, and import pylint worked in 0.07 seconds. PyPI separately declares GPL-2.0-or-later, which matters if you import and redistribute its internals.
Install Pylint inside the same environment as the application. Astroid follows imports, so a global or isolated tool environment sees third-party modules as missing unless you reproduce those dependencies or provide stubs and plugins. This is a common pre-commit surprise: an isolated hook may flag every project import. A local language: system hook uses the active project environment, while additional_dependencies can populate an isolated hook at the cost of duplicating dependency declarations.
Configuration can live in pyproject.toml, .pylintrc, pylintrc, or other supported locations. A nearer file can win over the one you edited, so use --verbose or generate configuration when results look inexplicable. Start a previously unlinted repository with --errors-only, then enable warnings and selected refactor or convention checks. Pylint returns a bit-encoded nonzero status when messages are present; decide whether CI should use --fail-on, --fail-under, or --exit-zero instead of assuming every message deserves the same gate.
Whole-package analysis gives Astroid more context than separate file invocations. -j 0 uses available CPUs, but parallel workers and CI sharding can weaken cross-file inference or duplicate-code checks. Dynamic libraries may need plugins such as pylint-django or generated-member configuration. Version 4.0.7 reduces false reports for TypedDict instances and PEP 695 generics, but it does not make inference perfect. Keep suppressions narrow, name the message, and enable useless-suppression periodically so obsolete comments do not accumulate.
Patterns
Check only error-category messages run-errors-only
pylint --errors-only src/my_packageThis is the practical first pass on an existing codebase. Pass the package directory so import and cross-module inference see more context than a list of isolated files.
Introduce warnings before style policy adopt-in-stages
# First deployment
pylint --errors-only src/my_package
# Add warnings while leaving convention and refactor checks off
pylint --disable=C,R src/my_packageTurn individual convention and refactor messages back on after the team agrees they catch something worth changing. A single all-default run often creates an ignored backlog.
Store a focused policy in pyproject.toml configure-pyproject
[tool.pylint.main]
py-version = "3.12"
jobs = 0
ignore-paths = ["^migrations/", "^tests/fixtures/"]
[tool.pylint."MESSAGES CONTROL"]
disable = ["missing-module-docstring", "too-few-public-methods"]
[tool.pylint.design]
max-args = 8The quoted MESSAGES CONTROL section keeps its space. Check for a nearer `.pylintrc` if edits here appear to have no effect.
Limit a suppression to the line that needs it suppress-one-message
import optional_backend # pylint: disable=import-error
def handler(event, context): # pylint: disable=unused-argument
return event['id']Use symbolic names so reviewers can see the reason. A standalone disable inside a block continues through that block unless you enable it again.
Fail CI on selected message classes gate-specific-errors
pylint --disable=all --enable=E,F --fail-on=E,F src/my_package--fail-on makes the named messages fatal even when another score setting would pass. Pin Pylint so an inference update cannot change the gate unexpectedly.
Require a minimum Pylint score gate-by-score
pylint --fail-under=9.0 src/my_packageThe score changes as files and enabled messages change. A list of fatal message IDs is easier to reason about when the goal is preventing bugs rather than tracking style debt.
Write structured results for CI tooling emit-json-report
pylint --output-format=json2 src/my_package > pylint-report.jsonjson2 includes messages plus run statistics. Keep normal text or colorized output for local work where clickable paths and concise explanations are easier to scan.
Use detected CPU cores for a full run use-parallel-workers
pylint -j 0 src/my_packageParallel analysis can change cross-file inference compared with a serial whole-package run. Keep the same worker policy locally and in CI when reproducible results matter.
Teach inference about a dynamic framework load-framework-plugin
pip install pylint-django
pylint --load-plugins=pylint_django src/my_projectInstall the plugin in the same environment as Pylint. Put load-plugins in project configuration so editor, terminal, and CI runs do not disagree.
Register a small organization-specific checker write-custom-checker
from astroid import nodes
from pylint.checkers import BaseChecker
class NoPrintChecker(BaseChecker):
name = 'no-print'
msgs = {'W9001': ('Use the application logger', 'forbidden-print', 'Keep output in structured logs.')}
def visit_call(self, node: nodes.Call) -> None:
if isinstance(node.func, nodes.Name) and node.func.name == 'print':
self.add_message('forbidden-print', node=node)
def register(linter) -> None:
linter.register_checker(NoPrintChecker(linter))Third-party checkers use message numbers in the 9000 range. The plugin module must be importable everywhere Pylint runs and must be listed in load-plugins.
Run Pylint in the project environment from pre-commit configure-pre-commit
repos:
- repo: local
hooks:
- id: pylint
name: pylint
entry: pylint --errors-only
language: system
types: [python]
require_serial: truelanguage: system lets Pylint see installed application dependencies. The tradeoff is that contributors must activate the correct environment before running pre-commit.
Draw package and class relationships with pyreverse generate-class-diagram
pyreverse -o svg -p billing src/billing
# produces classes_billing.svg and packages_billing.svg
pyreverse -o mmd -p billing src/billingSVG output needs Graphviz installed. Mermaid output is text and can be embedded in compatible Markdown renderers without a local Graphviz executable.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| ruff | PyPI | Choose it for very fast linting, broad rule coverage, formatting, and automatic fixes. |
| mypy | PyPI | Choose it when annotated type correctness is the main requirement rather than style and design checks. |
| flake8 | PyPI | Choose it for a smaller AST-based checker with a familiar plugin model and less inference. |
More cli & tooling guides
commander · chalk · typescript · esbuild · yargs · click · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

