Python 3.15: Explicit Lazy Imports Faster Startup Without Messy Imports
Python 3.15: Explicit Lazy Imports Faster Startup Without Messy Imports
On this page
- The Problem With Traditional Imports
- How Developers Worked Around It Before Python 3.15
- Python 3.15 Introduces lazy import
- A More Realistic Application Example
- What Happens When a Lazy Import Fails?
- Which Python Versions Support Lazy Imports?
- Should You Make Every Import Lazy?
- The Bigger Idea
- Final Takeaway
When a Python application starts, it doesn't just run your main() function. Python first has to load the modules your application imports.
For a small project, this usually isn't noticeable. But as an application grows, the import tree can become large. Python may load modules, execute their top-level code, and initialize dependencies that the application never actually uses during that particular run.
Python 3.15 introduces Explicit Lazy Imports through PEP 810, giving developers a built-in way to delay importing a module until it is actually needed.
The Problem With Traditional Imports#
Consider a simple application:
import json
import pandas
import numpy
import expensive_library
def main():
print("Application started")Even though main() never uses pandas, numpy, or expensive_library, Python normally imports them while the module is being initialized. In a large application, this can create unnecessary startup work.
This becomes particularly noticeable in applications such as:
- CLI tools
- serverless functions
- development tools
- large web applications
- applications with many optional features
The deeper the dependency tree becomes, the more work Python may perform before the application can actually start.
How Developers Worked Around It Before Python 3.15#
One common solution was to move the import inside the function that needs it:
def generate_report():
import expensive_library
return expensive_library.generate()This works, but it comes with a trade-off.
Your imports are no longer organized at the top of the module, and large codebases can end up with imports scattered across many functions.
Another approach is using importlib to load modules manually when required.
These techniques are useful, but they make the code responsible for managing something that is fundamentally part of Python's import system.
Python 3.15 provides a more explicit solution.
Python 3.15 Introduces lazy import#
Python 3.15 adds the lazy soft keyword for imports:
lazy import expensive_libraryThe module isn't loaded immediately.
Instead, Python creates a lazy proxy. The actual module is loaded when the imported name is first used.
For example:
lazy import json
print("Application started")
data = json.loads('{"name": "Python"}')At the point where the application prints:
Application startedjson has not yet been loaded.
When this line runs:
data = json.loads('{"name": "Python"}')Python loads the module because it is now actually needed.
You can also use it with from ... import:
lazy from pathlib import Path
path = Path("data.txt")The important idea is simple:
Declare the dependency now, but pay the import cost only when you use it.
A More Realistic Application Example#
Imagine a command-line application with several commands:
flowchart TD
myapp["📁 myapp/"]
myapp --> cli["📄 cli.py"]
myapp --> commands["📁 commands/"]
myapp --> services["📁 services/"]
commands --> report["📄 report.py"]
commands --> export["📄 export.py"]
commands --> migration["📄 migration.py"]
services --> database["📄 database.py"]
services --> analytics["📄 analytics.py"]
classDef folder font-weight:bold
class myapp,commands,services folderSuppose cli.py imports everything:
from commands.report import generate_report
from commands.export import export_data
from commands.migration import migrate_databaseStarting the CLI may initialize dependencies for all three commands, even when the user only wants:
python -m myapp --helpWith Python 3.15, dependencies that aren't immediately needed can be declared as lazy imports:
lazy from commands.report import generate_report
lazy from commands.export import export_data
lazy from commands.migration import migrate_databaseNow Python can defer loading those modules until their names are actually accessed.
This can be especially useful in large applications where many features share the same entry point but only a small portion of the application is used during each execution.
What Happens When a Lazy Import Fails?#
There is an important difference between a normal import and a lazy import.
With a normal import:
import missing_modulePython raises the import error while executing the import statement.
With:
lazy import missing_modulethe error can occur later, when the name is first accessed.
For example:
lazy import missing_module
print("Application started")
missing_module.run()The application can reach "Application started" before Python attempts to actually load missing_module.
This is one reason lazy imports should be used deliberately. They change when import-related errors happen.
Which Python Versions Support Lazy Imports?#
The lazy keyword is a Python 3.15 feature.
It was added as part of PEP 810 and is not available as normal Python syntax in Python 3.14 or earlier.
| Python version | lazy import |
|---|---|
| Python 3.14 and earlier | ❌ Not supported |
| Python 3.15+ | ✅ Supported |
Python 3.15 also provides __lazy_modules__ as another way to mark specific module imports as lazy:
__lazy_modules__ = ["json", "pathlib"]
import json
import pathlib
import os
Here, json and pathlib are treated as lazy imports, while os remains an ordinary eager import.
This option can be useful when working with code that needs a different way to declare lazy modules.
Should You Make Every Import Lazy?#
Not necessarily.
Lazy imports are primarily useful when startup time matters and some dependencies are expensive or unlikely to be used during a particular execution.
For example:
lazy import pandas
lazy import numpy
import logging
import sysIt may make sense to delay large optional dependencies while keeping lightweight, fundamental dependencies eager.
Lazy imports are therefore better viewed as a performance tool, not a replacement for every normal import.
The Bigger Idea#
PEP 810 isn't simply about adding a new keyword.
It addresses a common problem in growing Python applications:
Why should an application pay the cost of loading code that it never uses?
Before Python 3.15, developers could solve this with function-level imports or importlib. Python 3.15 makes lazy loading an explicit part of the language.
That means developers can keep imports organized while telling Python:
lazy import expensive_featureLoad it when I actually need it.
For large Python applications, that small change can help reduce unnecessary startup work while keeping the codebase easier to understand.
Final Takeaway#
Python 3.15's explicit lazy imports are not about making every application magically faster.
They are about giving developers more control over when dependencies are loaded.
If your application has a large dependency tree, expensive modules, or many optional features, lazy import is a new tool worth understanding when moving to Python 3.15.
Discussion
Loading the discussion…
Related articles
- How to Safely Rename a Django Model or Database FieldA practical, step-by-step guide to renaming Django models and fields without losing data or breaking production — covering RenameModel, RenameField, ForeignKeys, and zero-downtime strategies.Sep 15, 2026 · Django Migration
- How to Find and Fix N+1 Queries in DjangoA practical guide to finding N+1 query problems in Django with django-debug-toolbar and assertNumQueries, and fixing them with select_related, prefetch_related and Prefetch.Aug 30, 2026 · Django
- Redis Caching in Django: A Practical GuideHow to add Redis caching to a Django application without shipping stale data: cache backends, key design, the cache-aside pattern, invalidation on save and stampede protection.Aug 28, 2026 · Redis