Skip to content

Commit 9dab866

Browse files
authored
gh-148588: Document __lazy_modules__ (#148590)
1 parent c650b51 commit 9dab866

4 files changed

Lines changed: 79 additions & 2 deletions

File tree

Doc/reference/datamodel.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,7 @@ Attribute assignment updates the module's namespace dictionary, e.g.,
926926
single: __doc__ (module attribute)
927927
single: __annotations__ (module attribute)
928928
single: __annotate__ (module attribute)
929+
single: __lazy_modules__ (module attribute)
929930
pair: module; namespace
930931

931932
.. _import-mod-attrs:
@@ -1121,6 +1122,20 @@ the following writable attributes:
11211122

11221123
.. versionadded:: 3.14
11231124

1125+
.. attribute:: module.__lazy_modules__
1126+
1127+
A container (an object implementing :meth:`~object.__contains__`) of fully
1128+
qualified module name strings. When defined
1129+
at module scope, any regular :keyword:`import` statement in that module whose
1130+
target module name appears in this container is treated as a
1131+
:ref:`lazy import <lazy-imports>`, as if the :keyword:`lazy` keyword had
1132+
been used. Imports inside functions, class bodies, or
1133+
:keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are unaffected.
1134+
1135+
See :ref:`lazy-modules-compat` for details and examples.
1136+
1137+
.. versionadded:: 3.15
1138+
11241139
Module dictionaries
11251140
^^^^^^^^^^^^^^^^^^^
11261141

Doc/reference/simple_stmts.rst

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,56 @@ See :pep:`810` for the full specification of lazy imports.
920920

921921
.. versionadded:: 3.15
922922

923+
.. _lazy-modules-compat:
924+
925+
Compatibility via ``__lazy_modules__``
926+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
927+
928+
.. index::
929+
single: __lazy_modules__
930+
931+
As an alternative to using the :keyword:`lazy` keyword, a module can opt
932+
into lazy loading for specific imports by defining a module-level
933+
:attr:`~module.__lazy_modules__` variable. When present, it must be a
934+
container of fully qualified module name strings. Any regular (non-``lazy``)
935+
:keyword:`import` statement at module scope whose target appears in
936+
:attr:`!__lazy_modules__` is treated as a lazy import, exactly as if the
937+
:keyword:`lazy` keyword had been used.
938+
939+
This provides a way to enable lazy loading for specific dependencies without
940+
changing individual ``import`` statements. This is useful when supporting
941+
Python versions older than 3.15 while using lazy imports in 3.15+::
942+
943+
__lazy_modules__ = ["json", "pathlib"]
944+
945+
import json # loaded lazily (name is in __lazy_modules__)
946+
import os # loaded eagerly (name not in __lazy_modules__)
947+
948+
import pathlib # loaded lazily
949+
950+
Relative imports are resolved to their absolute name before the lookup, so
951+
:attr:`!__lazy_modules__` must always contain fully qualified module names.
952+
953+
For ``from``-style imports, the relevant name is the module following
954+
``from``, not the names of its members::
955+
956+
# In mypackage/mymodule.py
957+
__lazy_modules__ = ["mypackage", "mypackage.sub.utils"]
958+
959+
from . import helper # loaded lazily: . resolves to mypackage
960+
from .sub.utils import func # loaded lazily: .sub.utils resolves to mypackage.sub.utils
961+
import json # loaded eagerly (not in __lazy_modules__)
962+
963+
Imports inside functions, class bodies, or
964+
:keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are always eager,
965+
regardless of :attr:`!__lazy_modules__`.
966+
967+
Setting ``-X lazy_imports=none`` (or the :envvar:`PYTHON_LAZY_IMPORTS`
968+
environment variable to ``none``) overrides :attr:`!__lazy_modules__` and
969+
forces all imports to be eager.
970+
971+
.. versionadded:: 3.15
972+
923973
.. _future:
924974

925975
Future statements

Doc/whatsnew/3.15.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,18 @@ function, class body, or ``try``/``except``/``finally`` block raises a
184184
(``lazy from module import *`` and ``lazy from __future__ import ...`` both
185185
raise :exc:`SyntaxError`).
186186

187+
For code that cannot use the ``lazy`` keyword directly (for example, when
188+
supporting Python versions older than 3.15 while still using lazy
189+
imports on 3.15+), a module can define
190+
:attr:`~module.__lazy_modules__` as a container of fully qualified module
191+
name strings. Regular ``import`` statements for those modules are then treated
192+
as lazy, with the same semantics as the ``lazy`` keyword::
193+
194+
__lazy_modules__ = ["json", "pathlib"]
195+
196+
import json # lazy
197+
import os # still eager
198+
187199
.. seealso:: :pep:`810` for the full specification and rationale.
188200

189201
(Contributed by Pablo Galindo Salgado and Dino Viehland in :gh:`142349`.)

Misc/NEWS.d/3.15.0a8.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ dealing with contradictions in ``make_bottom``.
185185
.. nonce: 6wDI6S
186186
.. section: Core and Builtins
187187
188-
Ensure ``-X lazy_imports=none``` and ``PYTHON_LAZY_IMPORTS=none``` override
189-
``__lazy_modules__``. Patch by Hugo van Kemenade.
188+
Ensure ``-X lazy_imports=none`` and ``PYTHON_LAZY_IMPORTS=none`` override
189+
:attr:`~module.__lazy_modules__`. Patch by Hugo van Kemenade.
190190

191191
..
192192

0 commit comments

Comments
 (0)