Skip to content

Use lazy imports on Python 3.15 to improve startup speed#9733

Open
hugovk wants to merge 3 commits into
python-pillow:mainfrom
hugovk:lazy-imports
Open

Use lazy imports on Python 3.15 to improve startup speed#9733
hugovk wants to merge 3 commits into
python-pillow:mainfrom
hugovk:lazy-imports

Conversation

@hugovk

@hugovk hugovk commented Jun 29, 2026

Copy link
Copy Markdown
Member

Defer

The first commit here defers the import of tempfile in four files to the place where it's actually used, so we don't need to pay the import time if we're not using it. This helps for all Python versions.

So for example, only importing PIL.Image on Python 3.14:

pip install tuna
python3.14 -X importtime -c 'import PIL.Image' 2> import.log
tuna import.log
Before: 29 ms After: 23 ms
image image

Lazy

Python 3.15 introduces lazy imports:

If you put the new lazy keyword before an import, it won't be actually imported until first use. But lazy is only for 3.15+, it's a syntax error in 3.10-3.14.

For code that cannot use the lazy keyword directly (for example, when supporting Python versions older than 3.15 while still using lazy imports on 3.15+), a module can define __lazy_modules__ as a container of fully qualified module name strings. Regular import statements for those modules are then treated as lazy, with the same semantics as the lazy keyword

So the second commit introduces __lazy_modules__.

We can only put top-level imports here, and not those from function or in try/except. And we don't need to put any that used right away at the top-level, like logging in Image.py, but it's not a big deal if we do.

Repeating with 3.15:

Before: 22 ms After: 16 ms
image image

@codspeed-hq

codspeed-hq Bot commented Jun 29, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 331 untouched benchmarks


Comparing hugovk:lazy-imports (74eba11) with main (6590b1b)

Open in CodSpeed

@akx

akx commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

I think the

ElementTree: ModuleType | None
try:
    from defusedxml import ElementTree
except ImportError:
    ElementTree = None

import should also belong in getxmp()? It'd become a neat try: import; except: warnings.warn() early return too.

@hugovk

hugovk commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

Thanks, a couple extra "icicles" knocked off.

With 3.15, on a different machine compared to yesterday:

main: 26 ms PR before: 20 ms PR after: 17 ms
image image image

@akx

akx commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

It's certainly a bigger and unrelated change, but looks like Pillow only uses logging for a couple debug calls here and there in PIL.Image -- could be worth it to just get rid of it there (though real-world apps will generally probably import logging somehow either way, so additional laziness would need to be put in there in the stdlib...).

@hugovk

hugovk commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

I made the olefile import eager again: it's an optional dependency and lazy imports affect how its related plugins get registered.

I'll have a look at logging.

Unfortunately it's not straightforward to lazy import traceback in the stdib, because if we get an exception during shutdown, the import machinery might already be shutdown: python/cpython#150073.

lazy import re in logging might be more doable, but in any case won't show up until Python 3.16.

@hugovk

hugovk commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

Something like this?

--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
-import logging
...
-logger = logging.getLogger(__name__)
+class _LazyLogger:
+    """
+    Defer importing `logging` until the logger is first used to avoid slow import.
+    """
+
+    def __getattr__(self, attr: str) -> Any:
+        import logging
+
+        logger = logging.getLogger(__name__)
+        globals()["logger"] = logger
+        return getattr(logger, attr)
+
+
+logger = _LazyLogger()
Before: 16 ms After: 12 ms
image image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants