
Python 3.15 lazy imports: use it, with one caveat
PEP 810 adds a lazy keyword to Python 3.15. The speedup figures come from the PEP itself, so treat them as claims to test.
Use it: the lazy imports in Python 3.15 (PEP 810) are worth adopting now for command-line tools and slow-starting apps, but not for libraries that must run on older Pythons without care. The PEP's own claim of 50-70% faster CLI startup is a design-document figure, not a benchmark we reproduced.
The Weights Desk · 4 min read- Verdict: Use it for CLIs and startup-sensitive services; Wait for libraries that still support older Python versions.
- The deciding number is PEP 810's claimed 50-70% startup reduction for command-line tools, which comes from the PEP and not from our testing.
- lazy is a soft keyword allowed only at module scope; inside functions, classes or try blocks it raises SyntaxError.
- Import errors move from import time to first use, so a missing module can now fail late in production.
- A module-level __lazy_modules__ list gives older interpreters a migration path.
Python 3.15 ships explicit lazy imports, and the verdict for most application teams is Use it. The feature is PEP 810, now Final, and it lets a module defer loading a dependency until the name is first touched. The deciding number is the PEP's claim that command-line tool startup can fall 50-70% in practice. That is the PEP's figure, not a result we reproduced. Measure your own entry point before you rewrite imports.
What the lazy keyword actually does
The syntax is lazy import json or lazy from pathlib import Path. Instead of loading the module, Python binds a lightweight proxy of type types.LazyImportType. The module loads on first use of the name, and the proxy is then replaced by the real object. With lazy from, the first access loads the whole module, but only the accessed name is reified. Python 3.15's What's New page documents both forms, and the source post by Simon Willison highlights the same feature.
Where it is restricted
The restrictions are strict, which limits the blast radius. The lazy keyword is allowed only at module scope. Using it inside a function, a class body or a try, except or finally block raises a SyntaxError, as do lazy from m import * and lazy from __future__ imports. Global control exists through the -X lazy_imports flag, the PYTHON_LAZY_IMPORTS variable and sys.set_lazy_imports, plus an optional filter that can force chosen imports to stay eager.
The claimed gains are the PEP's, not ours
PEP 810 states that startup of command-line tools can drop by 50-70% in practice and that 30-40% memory savings were observed in real workloads. It also cites a 10-20% startup improvement for PySide applications and notes that about 17% of non-test standard library imports are already placed inside functions to defer loading. These are the proposal's own claims. We did not run a benchmark, so the figures are a hypothesis to test against your workload.
The operational risk: failures move to first use
Deferred loading changes when errors appear. If a lazily imported module is missing or broken, the exception is raised at first use rather than at startup, with a traceback that includes both the access site and the original import statement. In a service, that means a bad dependency could surface on a rarely exercised code path instead of failing fast at deploy. Smoke tests that touch every lazy name are the practical mitigation.
Migration path for older interpreters
For code that must run on older interpreters, PEP 810 offers a module-level __lazy_modules__ list of fully qualified names. Plain import statements for those modules then behave lazily on 3.15, with no new syntax. That lets a library keep one code path across versions, but it also means behavior differs between interpreters, so test both the eager and the lazy case before shipping a release.
The bottom line
Badge: Use it, for command-line tools and startup-sensitive applications. The one number that decides it is PEP 810's claimed 50-70% CLI startup reduction, which is the PEP's figure and not our measurement, so confirm it on your own entry point first. Wait if you maintain a library that supports older Pythons and cannot yet test both behaviors. Skip it for hot paths where first-use latency matters, and add smoke tests that touch every lazy name.
- What do explicit lazy imports do in Python 3.15?
- Writing lazy import json binds a lightweight proxy instead of loading the module. The module loads the first time the name is used, and the proxy is then replaced by the real object.
- Where is the lazy keyword not allowed?
- It is allowed only at module level. Inside functions, classes or try blocks it is a SyntaxError, and so are lazy from m import * and lazy from __future__ imports.
- Are the claimed speedups proven?
- Not by us. The 50-70% CLI startup and 30-40% memory figures are stated in PEP 810 as observed or practical results. Measure your own entry points before relying on them.
- What is the main operational risk?
- Import failures surface at first use rather than at startup, so a broken or missing dependency can fail on a rarely used code path. The PEP says the traceback includes both the access site and the original import statement.
- Hello Python 3.15, goodbye 3.10, and now I can use 3.11 — Simon Willison
- What's new in Python 3.15 — Python Software Foundation
- PEP 810 - Explicit lazy imports — Python Software Foundation