A Rust reimplementation of pylint that produces byte-for-byte identical output — 15–2300× faster (median ~85×).
prylint is not "inspired by" pylint. It is a bug-for-bug port: the same
messages, at the same lines and columns, with the same text, in the same
order, with the same exit codes and the same Your code has been rated
footer — verified byte-identically against real pylint on 52 production
codebases (~65,000 Python files), including django, numpy, pandas, sympy,
home-assistant, sqlalchemy, twisted, scikit-learn, and pylint's own functional
test suite. Where pylint has bugs, prylint reproduces them. Where pylint
crashes, prylint reports the same crash message.
pip install prylint
Requirements: a python3 (≥3.9) on PATH (used only to mirror pylint's
module-resolution paths and to reproduce CPython's exact syntax-error messages
for unparseable files). pylint and astroid themselves are not required.
Use it exactly like pylint — full check mode is the default:
prylint . # all checks (like `pylint .`)
prylint -E . # errors only (like `pylint -E .`)
prylint --disable=C0114,... . # same --disable / --enable / inline pragmas
Output, message order, exit codes, the score footer, --rcfile /
pyproject.toml discovery, init-hook, and # pylint: pragmas all match
pylint 4.0.5.
prylint . vs pylint . (both full check mode), pylint 4.0.5, Apple M-series,
single-threaded:
| codebase | pylint | prylint | speedup |
|---|---|---|---|
| black | 26.7 hr | 41s | 2328× |
| sentry | 3.7 hr | 24s | 546× |
| home-assistant (17.5k files) | 10.3 hr | 82s | 452× |
| airflow | 1.9 hr | 17s | 399× |
| salt | 1890s | 8.8s | 215× |
| zulip | 909s | 5.3s | 172× |
| django | 1524s | 10.1s | 150× |
| ansible | 419s | 2.9s | 143× |
| nova (OpenStack) | 1209s | 10.3s | 117× |
| fastapi | 116s | 1.0s | 120× |
| mypy | 367s | 3.9s | 95× |
| sqlalchemy | 614s | 7.1s | 87× |
| pandas | 1009s | 14.2s | 71× |
| scikit-learn | 613s | 9.6s | 64× |
| sympy | 1238s | 26s | 48× |
| …and 12 more, all ≥30× | |||
| aggregate (these 27) | 45.8 hr | 4.9 min | ~560× |
(These 27 are the large repos that are slow enough to time meaningfully; the full accuracy suite is 52 repos — see below.)
Median per-repo speedup ~85×; the aggregate is higher because pylint's
duplicate-code check (R0801) is O(n²) and dominates on test-heavy repos like
black. These are single-core numbers — the inference engine is
single-threaded to replicate astroid's order-sensitive global cache exactly
(see LIMITATIONS.md), and that byte-identical path is already
15–2300× pylint.
Every row above is also an accuracy test: each repo's full output is byte-identical to pylint's (see exceptions in LIMITATIONS.md).
prylint was built by differential testing against pinned pylint 4.0.5 / astroid 4.0.4 / CPython 3.12:
lookup 128, _metaclass_lookup_attribute 1024) with their exact
eviction, the 64-entry inference-tip FIFO, Uninferable propagation. Every
name/attribute/call node's inference is dumped and compared against astroid.# pylint: pragma handling
(disable/enable blocks, disable-next, skip-file), config-file discovery,
and exit-code bitmasks.Known, documented exceptions (one obscure SQLAlchemy class; the deliberately
excluded no-member family; the places pylint is nondeterministic against
itself) are catalogued in LIMITATIONS.md.
os.walk ordering, the
************* Module header rule, and the score-report footer).ast.parse exactly.scripts/setup_corpora.sh clones all 52 corpora at pinned commits and builds
the pinned pylint/astroid ground-truth venv. The accuracy contract: every
change must keep the corpora byte-identical (harness/ holds the differential
comparators).
GPL-2.0-or-later, the same license as pylint — prylint reproduces pylint's message texts and behavior verbatim.