Sample code for: Python 3.15 Preview: Lazy Imports - #808
Conversation
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.
Resolve the pyproject.toml conflict by keeping both ruff excludes: master's ai-benchmark and this branch's python315-lazy-imports. Also drop uv.lock. Master has no lockfile and pyproject.toml declares no [project] table, so the 3-line file was an incidental artifact of running uv inside the repo, unrelated to the tutorial's sample code.
bzaczynski
left a comment
There was a problem hiding this comment.
Verified during Final QA for the tutorial (CMS post 2288).
Merge conflict resolved. The branch was 10 commits behind and conflicted in pyproject.toml, where master had added ai-benchmark to [tool.ruff].exclude. Merged master in and kept both entries, so the diff against master is now a single clean hunk.
Dropped uv.lock. Master has no lockfile and pyproject.toml declares no [project] table, so the 3-line file was an incidental artifact of running uv in the repo rather than anything the tutorial needs.
Kept the ruff exclusion. Still justified: master pins ruff==0.14.1, which can't parse the lazy keyword, and the exclusion is what lets these files keep the style guide's one-blank-line rule so they stay byte-identical to the article's code blocks.
Code verified on CPython 3.15.0b3. Every file runs as the tutorial shows:
probe.py,partial.py,shapes.py,bridge.py,allmode.py,lazy_annotation.py— output matches the article exactly.- The deliberate failures all raise the documented exception:
badfunc.py(SyntaxError: lazy import not allowed inside functions),fail.py(chainedImportError→ModuleNotFoundError),type_checking_guard.py(NameErrorin the__annotate__frame), andcircular/eager,circular/init_eager,circular/init_lazy. circular/lazy/resolves the cycle, and deferring either side works in either import order.report_cli/: 145 ms eager vs 58 ms lazy on--help(220 → 73 modules), and 128 vs 126 ms under--load-all, confirming deferral costs nothing once the modules are used.cli_too_lazy.py --list-formatsprints the empty registry as intended.bench.py's return-code guard works — it refuses to run on 3.14 instead of timing a crashed process.
Repo gates pass locally with the pinned ruff: ruff format --check, ruff check, and .github/workflows/dircheck.py are all green, as is Linux314 on this head.
Article code blocks were diffed against these files: the 14 full-file blocks are byte-identical, and the three linenums="8" excerpts match their files exactly, with hl_lines="4-8" landing on the five lazy lines.
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
lazykeyword from PEP 810, which is aSyntaxErroron anything earlier. Every file here was run against a CPython 3.15.0rc1 build, and the tutorial quotes its real output.Layout
noisy_module.py,probe.pyshapes.py,partial.pybadfunc.pylazyIsn't Allowedreport_cli/type_checking_guard.py,lazy_annotation.pyif TYPE_CHECKINGDancefail.pycircular/bridge.py,allmode.pyreport_cli/holds three versions of the same CLI:cli_eager.py,cli_lazy.py(fivelazykeywords, nothing else changed), andcli_too_lazy.py, which also defers the two plugin imports and so silently empties the format registry.bench.pytimes any of them, and a--load-allflag 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, andinit_eager/+init_lazy/both fail with the sameImportErrorbecause the cycle needs a value during module initialization.One change outside the new folder
pyproject.tomladdspython315-lazy-importsto ruff'sexcludelist. Two reasons:lazykeyword.ruff format --checkfails withinvalid-syntax: Simple statements must be separated by newlines or semicolonson any file using it, which would break CI.CI passes locally with the exclusion in place:
ruff format --check,ruff check, anddircheck.pyare all green.