Speed boost your Python programs with new lazy imports

When you import a module in Python, the module’s code must be evaluated completely before the program can proceed. For most modules, this isn’t an issue. But if a module has a long and involved startup process, it’s going to slow down the rest of your program at the point where it’s imported.

Python developers typically work around this issue by structuring imports so they don’t happen unless they are needed—for instance, by placing an import in the body of a function instead of at the top level of a module. But this is a clumsy workaround, and complicates the flow of the program.

Python 3.15 adds a new feature, lazy imports, that provides a high-level solution for slow-importing modules. Declaring an import as “lazy” means it will be evaluated when it is first used, not when it is first imported. The cost of a slow import can then be deferred until the code it contains is actually needed. And, while lazy imports introduce new syntax, you can future-proof existing code to use them without having to change any of its syntax.

Eager versus lazy imports

To start, it’s helpful to understand the problem addressed by lazy imports. So, let’s say we have two files in the same directory:

# main.py
print ("Program starting")
from other import some_fn
print ("Other module imported")
some_fn()
print ("Program ended")

# other.py
print("Other module evaluation started")
from time import sleep
sleep(2)
# ^ This simulates a slow-loading module
print("Other module evaluation ended")

def some_fn():
    print ("some_fn run")

If you run main.py, the output should look something like this:


Program starting
Other module evaluation started

[two-second delay]

Other module evaluation ended 
Other module imported 
some_fn run 
Program ended

The mere act of importing other grinds our program to a near-halt before we can even do anything with the imported function, let alone continue with the rest of the program.

Now let’s see what happens if we modify main.py to use lazy imports (this will only work on Python 3.15 or higher):

print ("Program starting")
lazy from other import some_fn
print ("Other module imported")
some_fn()
print ("Program ended")

When you run the program now, the behavior changes:

Program starting
Other module imported
Other module evaluation started

[two-second delay]

Other module evaluation ended
some_fn run
Program ended

Now, the import imposes no delay at all. We only see the delay when we try to run the function we imported from the module.

What’s happening under the hood? When Python detects a lazy import—typically triggered with the lazy keyword on the import line, as shown above—it doesn’t perform the usual import process. Instead, it creates a “proxy object,” or a stand-in, for the imported module. That proxy waits until the program tries to do something with the module. Then the actual import action triggers, and the module is evaluated.

The lazy keyword is always the first word on the line of an import you want to declare as lazy:


# lazily imports foo
lazy import foo
# lazily imports bar from foo
lazy from foo import bar
# same with the use of "as":
lazy import foo as foo1
lazy from foo import bar as bar1

Where to use lazy imports in Python

The most common scenario for using lazy imports is to replace the usual workaround for avoiding a costly import at program startup. As I mentioned previously, placing the import inside a function, instead of at the top level of a module, causes the import to happen only when the function runs. But it also means the import is limited to the function’s scope, and is therefore unavailable to the rest of the module unless you apply another workaround.

With a lazy import, you can keep the import in the top level of a module as you usually would. The only change you have to make is adding the lazy keyword to your code.

Using lazy imports automatically

It is also possible to enable imports on an existing codebase automatically—without rewriting any import statements.

Python 3.15 adds new features to the sys module that control how lazy imports work. For instance, you can declare programmatically that every import from a given point forward in your program’s execution will be lazy:

import sys
sys.set_lazy_imports("all")

If sys.set_lazy_imports() is given "all", then every import in the program from that point on is lazy, whether or not it uses the lazy keyword. Code labeled "normal" would have only explicitly lazy imports handled as lazy, and code labeled "none" would disable lazy importing across the board.

Controlling lazy imports programmatically

You can also hook into lazy imports at runtime, which lets you do things like control which specific modules are lazy-imported:


import sys

def mod_filter(importing, imported, fromlist):
    return imported == ("module")

sys.set_lazy_imports_filter(mod_filter)
sys.set_lazy_imports("all")

sys.set_lazy_imports_filter() lets you supply a function that takes in three parameters:

  • The module where the import is being performed
  • The module being imported
  • A list of names being imported

With that, you can write logic to return True to allow a given import to be lazily imported, or False to force it to be imported normally. This lets you write allow-lists and block-lists for lazy imports as part of a test, or simply as part of how your program works.

Two ways to get started with lazy imports

Python has a long-standing tradition of allowing newer features to be added gracefully to existing codebases. Lazy imports can be used the same way: You can check for the presence of the feature at program start, then apply lazy imports across your codebase automatically by using sys.set_lazy_imports().

To start, you can check the Python version number:

import sys
if (sys.version_info.major==3 and sys.version_info.minor>=15):
    ... # set up lazy imports

Or you can test for the presence of the lazy import controls in sys:

import sys
if getattr(sys, "set_lazy_imports", None):
    ... # set up lazy imports

Go to Source

Author: