Python Analysis: PEP 8 in the Standard Library
A fixed-instrument measurement of PEP 8 conformance in CPython's Lib/, across every release python.org offers, from 2.0.1 to 3.14, with its data and charts.
Does Python's standard library follow PEP 8, the style guide written for it? This project measures it the same way for every release python.org offers, 264 of them from 2.0.1 to 3.14.7, plus the second release candidate of 3.15.0 as a stand-in. The instruments are fixed: pycodestyle 2.15.0, the reference implementation of PEP 8's checkable rules, and ruff 0.14.4 as a cross-check, both with every configuration file ignored, at 79 columns, on the same explicit file list. The result is a rate per thousand lines, split into the library proper, its tests and its generated files.
What it found
- The library proper conforms about twice as well as it did in 3.0. It went from 87.6 to 48.1 style diagnostics per thousand lines between 3.0 (2008) and 3.14.0 (2025), falling at every step but one. The test suite went from 99.1 to 56.0, and the two instruments agree within 2.5% at every point.
- Python 2 was worse. 2.0.1 runs at 261 per thousand lines and 2.7 at 129. Half of 2.0.1's count is indentation with tabs.
- Every rule family fell but line length. Line length rose from 5.5 to 10.9 per thousand lines, and E501 is the only rule in the top twenty with more instances in 3.14.0 (2,951) than in 3.0 (886). Long lines rise from 2 per thousand in code untouched since before PEP 8 to 28 in code edited since 2023, though the median long line is 85 characters, not 99.
- A naive count measures the wrong thing. In 3.14.0 the library proper holds 17% of the diagnostics on 28% of the lines, the test suite 47% on 67%, and 78 generated files, 4.5% of the lines, hold 36% (the charmap codecs are tables of one-line entries with inline comments).
- It is layout, not code. The rules about what code does (comparisons to None, bare excepts, lambda assignments and the like) run at 1.1 per thousand lines in 3.14.0, 308 instances, 149 of them bare excepts. Ninety-eight per cent of the count is layout, and against black-formatted projects, which sit near zero on everything but line length, the library is an order of magnitude further from PEP 8's layout rules.
How it is measured
Every tarball comes from python.org, and data/tarballs.sha256 records which bytes were measured. The series is the first release of each minor; every other release is a sensitivity set, and no patch release of 3.x drifts more than 7.5 per thousand lines (over the whole of Lib/) from its minor's first. Beyond the series, the repository dates every line of 3.14.0 by the commit that last edited it (git blame at v3.14.0, and a second dating with -w -M -C), and measures the line-length distribution and the 72-column docstring rule, naming through pep8-naming, what CPython's own ruff hooks enforce, which violations of the 3.8.0 branch point survive in 3.14.0, density per package, a bootstrap over files, and the same instruments on Django, NumPy, pip, requests and black. The protocol is PROTOCOL.md, and RESULTS.md carries the tables as the harness prints them.
Running it
make setup # uv: a .venv with pycodestyle 2.15.0 and ruff 0.14.4 under Python 3.14 make fetch # the tarballs into work/ (about 450 MB down, 2.2 GB on disk) make measure # both instruments on every release, about 3 s each make results # the RESULTS.md tables make cpython # clone CPython once (about 1 GB); the blame needs the history make analyses # the cohorts and the other measurements
Where it came from
It grew out of the November 2025 post Python PEP8: Practice what you preach and its script, which is the repository's history up to fe6a6fa. That post ran 25 of ruff's rules at 88 columns and saw a rising trend in the latest releases. The validation/ directory reproduces its numbers exactly and shows the trend to be a configuration artifact: from 3.13.8 the CPython tarball ships its own .ruff.toml, which the 2025 pipeline honoured for those releases only. Where the releases were measured alike, the direction was the same; the level was under a tenth (4,479 diagnostics for 3.0.1, against 59,804 here), because most of pycodestyle was not run.