Python 3.15 Lazy Imports: Loading Time, Side Effects, and Compatibility
Use explicit lazy imports in Python 3.15 and check deferred errors, initialization order, and compatibility with older Python versions.
Python 3.15's lazy import loads a module when an imported name is first used, rather than when the declaration runs. This can avoid unnecessary startup work in a CLI where different commands use different dependencies. Import errors and initialization side effects also move later, so check execution order before converting imports in bulk.
A conceptual illustration of a module opening when needed; this is not an application screenshot.
As of 2026-11-07, the examples below are illustrative and based on the official documentation; they have not been separately executed on Python 3.15. The references are What's New in Python 3.15 and PEP 810.
Separate declaration from loading
A regular import loads a module when the statement executes. An explicit lazy import binds a lazy object and performs the actual import when that name is first used. Put these two files in the same directory:
The documented semantics imply the order application ready, loading feature, then running feature. A path that never reaches slow_feature.run() does not load it through that name. Other code can still import the same module first, so one lazy declaration does not guarantee it remains unloaded across the entire process. lazy from package import name is supported too. Official explanation
Errors and initialization happen later
If a module is missing or its import fails, the exception occurs at first use instead of at the declaration. An application that previously detected dependency problems at startup may now fail only when a feature is invoked. Deployment smoke tests should actually invoke each feature to trigger its deferred imports.
Module-level code that registers plugins, configures logging, or changes a registry also runs later. If other code relies on those effects, keep the import eager or call an explicit initialization function. Changing load order can change behavior; it is not merely a performance switch. PEP 810 compatibility
Syntax and older Python versions
The lazy syntax is permitted only at module scope. It cannot be used in a function, class body, try block, or star import. Python 3.14 and earlier cannot parse the new syntax: wrapping it in a version conditional does not prevent a parse error. Syntax restrictions
For code that also supports older releases, the official documentation describes __lazy_modules__:
Python 3.15 treats the specified import as lazy; older versions execute it as a regular import. Check module names and package structure against the documentation, and test every supported version. This does not add lazy loading to older interpreters.
Before adopting it
- Measure actual CLI startup time and loaded modules first. Imports may not be the bottleneck.
- Change one heavy dependency used by an infrequent command at a time. Check dependencies before deferring modules with shared initialization or registration side effects.
- Exercise each command's normal path and missing-dependency path. Check when errors occur, their messages, and initialization order.
- Measure both startup and first feature invocation before and after, in the same environment. Cost may move to the first call rather than disappear; this article does not report a measured performance gain.
- Record Python and package versions and CI coverage. Explicit declarations make the affected scope easier to understand than a global lazy mode.
Global options and filters are also available, but they can change third-party initialization order. Start with explicit declarations and verify the behavior in the actual deployment environment. PEP 810 global control