Skip to content
>_Rong

Search the full text of every article — titles, tags, categories and article content.

Python 3.15: Explicit Lazy Imports Faster Startup Without Messy Imports

Python 3.15: Explicit Lazy Imports Faster Startup Without Messy Imports

saroeun4 min readPython
On this page

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_library

The 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 started

json 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 folder

Suppose cli.py imports everything:

  from commands.report import generate_report
  from commands.export import export_data
  from commands.migration import migrate_database

Starting the CLI may initialize dependencies for all three commands, even when the user only wants:

  python -m myapp --help

With 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_database

Now 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_module

Python raises the import error while executing the import statement.

With:

  lazy import missing_module

the 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 versionlazy 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 sys

It 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_feature

Load 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…