What is lazy in Python 3.15 and why it's so exciting?

What is lazy in Python 3.15 and why it's so exciting?

by: Manuel 5 min read 0 comments Save

I really like when laziness is a feature, not a bug, especially in code. There's a saying that good developers are lazy ones, so why shouldn't Python become lazy as well?

Jokes aside, Python 3.15 introduces lazy imports, and this is a big deal.

In case you don't know, when you write import xxx and run the code, Python does as it's told and imports the dependency so that it can run the code.

So for example:

import pandas as pd

# Other application code

def could_be_called() -> None:
    # Use pandas here
    ...

The issue is that, by convention, all imports go at the top of the file. This means that we import everything we need first and then use the dependencies in the code.

Python would import pandas even if the function is never used, and that's a waste of resources.

I know that it's not significant for 1 or 2 imports or simpler scripts, but when projects become big and complex, many dependencies slow the code down, since a lot of "stuff" needs to be loaded into memory before Python can do something useful.

A common workaround would be to import only when you need the dependency, like this:

...

def could_be_called() -> None:
    import pandas as pd
    # Use pandas here

...

This works, and indeed it only loads if we need the function, but in my opinion it's ugly and makes the code hard to read.

Now we can use a lazy import, which looks like this:

lazy import pandas as pd

def could_be_called() -> None:
    # Use pandas here
    ...

You declare all imports at the beginning, and Python only loads them if needed.

Looking at the documentation:

This mechanism is particularly useful for applications that import many modules at the top level but may only use a subset of them in any given run. The deferred loading reduces startup latency without requiring code restructuring or conditional imports scattered throughout the codebase.

The developers even thought (and thanks for that) of people like us with big codebases that could benefit from this. Again, from the documentation:

For cases where you want to enable lazy loading globally without modifying source code, Python provides the -X lazy_imports command-line option and the PYTHON_LAZY_IMPORTS environment variable. Both accept two values: all makes all imports lazy by default, and normal (the default) respects the lazy keyword in source code. The sys.set_lazy_imports() and sys.get_lazy_imports() functions allow changing and querying this mode at runtime.

So running your code with python -X lazy_imports=all main.py, or setting PYTHON_LAZY_IMPORTS=all, makes all imports lazy automatically.

How to try this?

If you're using uv, it's quite simple. uv is a tool that installs Python versions and manages your project's dependencies. Go to your project and:

  1. Update uv to the latest version, otherwise you won't see Python 3.15 in uv python list.
  2. uv python install 3.15: This installs the latest Python 3.15 build. If you already have an older 3.15 build, run uv python upgrade 3.15 instead.
  3. uv venv -p 3.15.0rc3: This replaces the current virtual environment (venv) with Python 3.15. In my case, it's a release candidate (RC) because of the time of writing, but check what's available to you.
  4. Activate the environment again with source .venv/bin/activate.
  5. Check that Python is the right version with python --version.
  6. Then run uv sync to update the dependencies. It's possible that some may not work in case they were not updated yet.

Run the code as usual.

Let's try it out

Let's use a simple example:

import pandas as pd

def main():
    print("Hi from manueltgomes.com")

if __name__ == "__main__":
    main()

In this case, pandas is a good example because it's a fairly large library with its own dependencies. Notice that we don't even use it, and that doesn't matter. Without lazy, it's loaded nevertheless, and that's still the default behavior in 3.15.

Let's look at the same code, just adding lazy, and see the difference. I saved that version as main_lazy.py, and the only change is lazy import pandas as pd.

lazy import pandas as pd

def main():
    print("Hi from manueltgomes.com")

if __name__ == "__main__":
    main()

To do a fair comparison, I'm running the code 20 times in three ways: as is, with the lazy keyword, and with the -X lazy_imports=all flag, so that we get a decent average. Also, I'm doing 3 warm-up runs first (not counted in the results) so that all code runs in a comparable state.

Warning

The following numbers are based on a simple example, so please consider them as indicative only. Your results may vary a lot with the code and dependencies, so please test your code, draw your own conclusions, and see if it works for you.

Here are the results.

Variant Command Mean Median Stdev Min Max Speedup vs eager
Eager python main.py 240.6 ms 241.1 ms 5.9 ms 226.0 ms 252.8 ms 1.0x
Lazy keyword python main_lazy.py 18.3 ms 17.7 ms 2.0 ms 16.7 ms 23.9 ms 13.2x (222.3 ms saved)
Eager + -X flag python -X lazy_imports=all main.py 18.7 ms 17.8 ms 2.0 ms 17.5 ms 25.0 ms 12.9x (221.9 ms saved)
Eager vs Lazy

Eager means that imports load right away when the script starts, while lazy loads them only when they're first needed.

Getting roughly a 13x speedup by adding one word or parameter is amazing, so congrats to the Python team for this implementation.

Final Thoughts

I would recommend downloading the latest version of Python that you can access (at the time of publishing, it's Release Candidate 3) and giving it a shot. Python itself had some nice speed improvements, and with the lazy option, it becomes quite fast in certain situations.

Notice that some packages may not be available or have issues, so be prepared to test your code so that you don't ship failing features.

Sources

Photo by elizabeth lies on Unsplash

Comments

Spotted a mistake or have a better approach? Let me know. I read and reply to every one.

💬

No comments yet

Be the first to share your thoughts on this article!

Leave a Comment

All comments are reviewed for spam before being displayed 5000 left
Replying to