Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,7 @@ Attribute assignment updates the module's namespace dictionary, e.g.,
single: __doc__ (module attribute)
single: __annotations__ (module attribute)
single: __annotate__ (module attribute)
single: __lazy_modules__ (module attribute)
pair: module; namespace

.. _import-mod-attrs:
Expand Down Expand Up @@ -1121,6 +1122,20 @@ the following writable attributes:

.. versionadded:: 3.14

.. attribute:: module.__lazy_modules__

A container (an object implementing :meth:`~object.__contains__`) of fully
qualified module name strings. When defined
at module scope, any regular :keyword:`import` statement in that module whose
target module name appears in this container is treated as a
:ref:`lazy import <lazy-imports>`, as if the :keyword:`lazy` keyword had
been used. Imports inside functions, class bodies, or
:keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are unaffected.

See :ref:`lazy-modules-compat` for details and examples.

.. versionadded:: 3.15

Module dictionaries
^^^^^^^^^^^^^^^^^^^

Expand Down
50 changes: 50 additions & 0 deletions Doc/reference/simple_stmts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,56 @@ See :pep:`810` for the full specification of lazy imports.

.. versionadded:: 3.15

.. _lazy-modules-compat:

Compatibility via ``__lazy_modules__``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. index::
single: __lazy_modules__

As an alternative to using the :keyword:`lazy` keyword, a module can opt
into lazy loading for specific imports by defining a module-level
:attr:`~module.__lazy_modules__` variable. When present, it must be a
container of fully qualified module name strings. Any regular (non-``lazy``)
:keyword:`import` statement at module scope whose target appears in
:attr:`!__lazy_modules__` is treated as a lazy import, exactly as if the
:keyword:`lazy` keyword had been used.

This provides a way to enable lazy loading for specific dependencies without
changing individual ``import`` statements. This is useful when supporting
Python versions older than 3.15 while using lazy imports in 3.15+::

__lazy_modules__ = ["json", "pathlib"]

import json # loaded lazily (name is in __lazy_modules__)
import os # loaded eagerly (name not in __lazy_modules__)

import pathlib # loaded lazily

Relative imports are resolved to their absolute name before the lookup, so
the sequence must always contain fully qualified module names. For
``from``-style imports, the name to list is the module being imported
*from* (the base module, left of the ``import`` keyword), not the names
imported from it::

# In mypackage/mymodule.py
__lazy_modules__ = ["mypackage", "mypackage.sub.utils"]

from . import helper # loaded lazily: . resolves to mypackage
from .sub.utils import func # loaded lazily: .sub.utils resolves to mypackage.sub.utils
import json # loaded eagerly (not in __lazy_modules__)

Imports inside functions, class bodies, or
:keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are always eager,
regardless of :attr:`!__lazy_modules__`.

Setting ``-X lazy_imports=none`` (or the :envvar:`PYTHON_LAZY_IMPORTS`
environment variable to ``none``) overrides :attr:`!__lazy_modules__` and
forces all imports to be eager.

.. versionadded:: 3.15

.. _future:

Future statements
Expand Down
12 changes: 12 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ function, class body, or ``try``/``except``/``finally`` block raises a
(``lazy from module import *`` and ``lazy from __future__ import ...`` both
raise :exc:`SyntaxError`).

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
:attr:`~module.__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::

__lazy_modules__ = ["json", "pathlib"]

import json # lazy
import os # still eager

.. seealso:: :pep:`810` for the full specification and rationale.

(Contributed by Pablo Galindo Salgado and Dino Viehland in :gh:`142349`.)
Expand Down
4 changes: 2 additions & 2 deletions Misc/NEWS.d/3.15.0a8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ dealing with contradictions in ``make_bottom``.
.. nonce: 6wDI6S
.. section: Core and Builtins
Ensure ``-X lazy_imports=none``` and ``PYTHON_LAZY_IMPORTS=none``` override
``__lazy_modules__``. Patch by Hugo van Kemenade.
Ensure ``-X lazy_imports=none`` and ``PYTHON_LAZY_IMPORTS=none`` override
:attr:`~module.__lazy_modules__`. Patch by Hugo van Kemenade.

..
Expand Down
Loading