Skip to content

Add PY_FORCE_LAZY_IMPORTS env var for deferred module loading#1

Open
logansnow17 wants to merge 7 commits into
3.13from
lazy-imports
Open

Add PY_FORCE_LAZY_IMPORTS env var for deferred module loading#1
logansnow17 wants to merge 7 commits into
3.13from
lazy-imports

Conversation

@logansnow17

Copy link
Copy Markdown
Owner

Implements lazy imports inspired by PEP 690 and HRT's CPython fork. When PY_FORCE_LAZY_IMPORTS=true is set, top-level import foo statements are deferred until the module's attributes are first accessed. This can significantly reduce startup time for applications with heavy imports.

  • Lib/_lazy_imports.py: LazyModule proxy type and import hook
  • Lib/site.py: activate lazy imports based on env var at startup
  • Lib/test/test_lazy_imports.py: 15 unit and integration tests

logansnow17 and others added 7 commits March 30, 2026 12:02
Implements lazy imports inspired by PEP 690 and HRT's CPython fork.
When PY_FORCE_LAZY_IMPORTS=true is set, top-level `import foo` statements
are deferred until the module's attributes are first accessed. This can
significantly reduce startup time for applications with heavy imports.

- Lib/_lazy_imports.py: LazyModule proxy type and __import__ hook
- Lib/site.py: activate lazy imports based on env var at startup
- Lib/test/test_lazy_imports.py: 15 unit and integration tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch from __new__ to __init__ so the full_name kwarg is consumed
by LazyModule rather than leaking to ModuleType.__init__.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dotted imports (import a.b.c) must be eager because they need to
actually load submodules as a side effect. Previously the hook would
skip loading submodules when the top-level package was already in
sys.modules, breaking code like `import email.mime.text` where
internal submodule imports rely on sibling submodules being loaded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use __getattribute__ instead of __getattr__ so that attributes which
exist on the LazyModule itself (like __dict__, __name__, __spec__)
are properly delegated to the real module after resolution. This fixes
ssl, enum._convert_, and anything else that inspects module.__dict__
on a lazily-imported module.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use importlib.util.find_spec() to check that a module actually exists
before creating a lazy stub. Without this, conditional imports like
`try: import brotli except ImportError: ...` would silently succeed,
returning a lazy stub, and only fail later when an attribute is
accessed -- far from the try/except guard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When `from sqlalchemy.engine import Engine` was called and `sqlalchemy`
was a LazyModule, the bootstrap's _find_and_load_unlocked would access
parent.__path__, triggering _resolve() mid-flight. The resolution would
transitively load sqlalchemy.engine as a side effect, then the original
_find_and_load_unlocked would resume and load it AGAIN from scratch,
creating a new module object that lacked the 'base' attribute.

Fix: resolve all lazy ancestors in the parent chain *before* calling
_original_import, so the bootstrap never encounters a LazyModule when
traversing __path__. Also use __getattribute__ instead of __getattr__
to properly proxy __dict__ and other built-in module attributes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extends lazy imports to also defer `from X import Y` statements.
When the env var is set, `from foo import bar` returns a transparent
proxy that resolves on first use (call, attribute access, operators,
class inheritance via __mro_entries__, etc).

Key design decisions:
- Only top-level from-imports are deferred; transitive imports during
  module loading (relative imports, resolve chains) stay eager via
  _import_depth tracking
- __mro_entries__ delegates to the real object's implementation (e.g.
  Generic) so metaclass machinery works correctly
- Comprehensive dunder methods on the proxy for transparent operation
- Core stdlib modules excluded to avoid bootstrap issues

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant