Python 3.15 makes deferred imports explicit—and changes when failure happens
Python 3.15 adds opt-in lazy imports that defer module loading until first use. The startup win comes with a changed point of execution: errors and import-time effects move to the path that touches the name.
A startup optimization with a visible boundary
Python 3.15.0 became the stable release on 9 October 2026. Among its language changes is PEP 810, which adds explicit lazy imports. The new form lets a module keep imports grouped near the top of its file while asking Python to postpone loading selected dependencies. That is useful when a command-line tool, service or desktop program imports a broad dependency tree but only some branches run in a typical invocation. [1] [2]
The important design choice is opt-in. An ordinary import still has its established eager behavior. A programmer marks a particular import with the lazy soft keyword, as in lazy import json or lazy from pathlib import Path. Python binds a lazy proxy at that point; the module is loaded when code first uses the imported name. PEP 810 makes this a language feature rather than requiring each project to invent its own import hook or move statements into scattered functions. [3]
The work moves to the first use
Deferral changes when work happens, not whether an imported module is eventually executed. If lazy from reports.pdf import Renderer is followed by Renderer(...), Python loads and executes the complete reports.pdf module, then resolves Renderer. Other definitions and top-level statements in that file run too. The syntax does not extract one class from a module without running the module. This is the same basic whole-module execution rule as an eager from import; only the timing differs. [3]
That shift changes the shape of failures. If the dependency is missing, contains a syntax error, or fails during initialization, code may pass application startup and encounter the error on the first request or command that touches the lazy name. A short-lived command that never uses a dependency can avoid its loading cost entirely. A long-running server may simply transfer that cost to the first relevant request, where extra latency is more visible. PEP 810 describes import errors as occurring when the lazy binding is first used. [3]
Imports can also establish state. A module may register a plugin, install a codec, configure logging or patch another library when its top-level code runs. If the importing module relied on that effect happening before any other work, deferring the import can silently change order or leave the registration absent on an unused path. A useful design improvement is to make setup an explicit function and call it at the point where the application needs that capability, rather than depending on import order as hidden initialization. [3]
Use laziness where the program has a real branch
A practical candidate is an optional export feature in a command-line application. The program may parse arguments and print help on every run, while only export needs a large spreadsheet library. If that dependency is imported eagerly, even tool --help pays its discovery, reading, compilation and module execution costs. Marking the import lazy can keep it out of the help path, provided the export branch reliably uses the imported name before needing its behavior. The improvement is a hypothesis about that workload, not a performance result: filesystem caches, dependency size, interpreter version and what the program actually touches all matter.
The syntax is deliberately constrained. PEP 810 allows lazy imports at module level, but not inside a function, class body, try block or wildcard import; future imports cannot be lazy. Those restrictions keep the declaration in a predictable location and avoid treating every import shape as deferrable. They also mean a developer cannot wrap a lazy import in a local exception handler to catch a missing dependency at the point of declaration. [3]
Keep an import eager when its initialization is required at startup, when an early and predictable failure is part of the program contract, or when delaying it would put unpredictable work on a latency-sensitive path. Laziness is most straightforward for optional features with a clear activation point and no required import-time side effects. Explicit initialization can make other modules safe to defer, but should itself be tested as part of the relevant feature path. [3]
Measure both sides of the boundary
For an upgrade to Python 3.15, first record which command or service startup matters and profile its imports. Then compare eager and explicitly lazy versions under the same conditions. Measure process startup separately from the first use of the deferred feature and from later uses. A lower startup time can coexist with a slower first export; measuring only one side makes that trade invisible. Python’s 3.15 release documentation identifies explicit lazy imports as a startup improvement, while PEP 810 describes the changed timing and behavior that accompany it. Neither means every application will become faster after a mechanical conversion. [2] [3]
Finally, exercise the paths that matter: normal startup, help or health-check paths, the first invocation of each optional feature, missing dependencies, and modules with registration side effects. Review introspection or tooling that expects a real module immediately after the import statement. The release makes deferred loading a standard, visible choice. A sound migration still treats import timing as part of the program’s behavior and measures where the work—and any failure—now occurs.