Your command-line tool imports a large library at the top of its main module.
Every run waits for that import, even mytool --help, which never uses the
library. To see where startup time goes, run:
python -X importtime -c "import mytool"
Python prints a line for each module it imports, with the time in microseconds.
The usual fix is to move the import into the function that needs it:
def export_report(rows: list[dict[str, str]]) -> str:
import json # later calls find json already loaded
return json.dumps(rows)
That works, but now the import is inside a function, not with the other imports at the top of the file.
Mark an import as lazy
Python 3.15 adds lazy import. The import stays at the top of the file, and
Python loads the module the first time your code uses the name:
import sys
lazy import json
print("json" in sys.modules) # False, nothing loaded yet
def export_report(rows: list[dict[str, str]]) -> str:
return json.dumps(rows) # the first use loads json
export_report([])
print("json" in sys.modules) # True
sys.modules is Python's record of the modules it has loaded. Until the
first use, the name json holds a placeholder object of type lazy_import.
lazy from json import dumps works the same way, and the first use of
dumps loads the whole json module.
You can write lazy only at the top level of a module, including inside an
if or a with block there. It is a SyntaxError inside a function, a
class or a try block, and with from module import *.
Work moves from startup to first use
A lazy import does the same work later. Three things move with it.
- Errors.
lazy import missing_modulesucceeds at startup. TheModuleNotFoundErrorcomes at the first use, perhaps in the middle of a request. The traceback shows the line that used the name and thelazy importline. - Side effects. Some modules do work when they load, such as a plugin that adds itself to a registry. If your code never uses the name, the module never loads, and that work never happens.
- Time. The first call that uses the module also loads it, so that call is slower. Measure that call as well as startup.
Check your understanding A plugin module registers itself when it loads, and your app imports it only for that. Can you make that import lazy? Show answer Hide answer
No. Nothing uses the name, so the module never loads and the plugin never
registers. Keep that import eager, or give the plugin a register() function
that your app calls. Test startup in a fresh process, because an earlier test
may already have imported the plugin and hidden the bug.
Try it without the new keyword
A module can list names in __lazy_modules__. Python 3.15 then treats a plain
import of those names as lazy:
__lazy_modules__ = ["json"]
import json # lazy on 3.15, an ordinary import on 3.12 to 3.14
Older versions see an ordinary list and import json at once, so the file
still runs there. The lazy keyword itself is a SyntaxError on Python 3.14
and older, and a version check in the same file cannot hide it.
Write Code That Runs on Several Python Versions
covers your choices.
Control lazy imports for a whole program
Python 3.15 has two modes:
normal, the default. Only the imports you marked are lazy.all. Every import at the top level of a module is lazy, except imports inside atryblock.
Choose the mode with -X lazy_imports=all, the PYTHON_LAZY_IMPORTS
environment variable, or sys.set_lazy_imports("all"). No mode turns
laziness off. Any other value, such as none, makes
sys.set_lazy_imports raise ValueError, and stops Python at startup when
you pass it with -X or the environment variable.
To keep chosen imports eager, give sys.set_lazy_imports_filter a function.
Python calls it for each import that would be lazy, including the ones you
marked. It passes the importing module's name, the imported module's name and
the names after from ... import. Return False to load that module at once.
import sys
def keep_plugins_eager(
importer: str | None, name: str, fromlist: tuple[str, ...] | None
) -> bool:
return not name.startswith("myapp.plugins")
sys.set_lazy_imports_filter(keep_plugins_eager)
sys.set_lazy_imports("all")
Mode all changes every module your program imports, including other
people's libraries. After you turn it on, run your whole test suite in a
fresh process.
Going further
The lazy keyword comes from PEP 810. To check which mode a program runs
in, call sys.get_lazy_imports(), which returns "normal" or "all".