bench.py ignored the subprocess return code, so a script that crashed
was timed as if it had succeeded. A reader whose python isn't 3.15
would have seen cli_lazy.py die with a SyntaxError and bench.py report
it as a 2.5x speedup:
$ python3.14 bench.py cli_lazy.py --help
cli_lazy.py: 50 ms (best of 10)
That's the same silent, plausible-looking wrongness the tutorial warns
about in its own side-effects section. bench.py now checks the return
code, surfaces the captured stderr, and refuses to run at all below
3.15.
Also note in the README which files raise on purpose, and that a pyenv
build needs Tk headers or cli_eager.py won't start.
Sample code for the Real Python tutorial Python 3.15 Preview: Lazy Imports.
Standard library only, but it needs Python 3.15 — most files use the lazy keyword from PEP 810, which is a SyntaxError on anything earlier. Every file here was run against a CPython 3.15.0rc1 build, and the tutorial quotes its real output.
Layout
report_cli/ holds three versions of the same CLI: cli_eager.py, cli_lazy.py (five lazy keywords, nothing else changed), and cli_too_lazy.py, which also defers the two plugin imports and so silently empties the format registry. bench.py times any of them, and a --load-all flag reads every deferred name so you can check that deferral costs nothing once the modules are used.
circular/ has four self-contained folders: eager/ fails, lazy/ is fixed by deferring one side, and init_eager/ + init_lazy/ both fail with the same ImportError because the cycle needs a value during module initialization.
One change outside the new folder
pyproject.toml adds python315-lazy-imports to ruff's exclude list. Two reasons:
CI passes locally with the exclusion in place: ruff format --check, ruff check, and dircheck.py are all green.