From 543527b68e8b260542848208175f07eb4556c7ca Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 02:07:00 +0200 Subject: [PATCH 01/14] gh-148588: Document __lazy_modules__ compatibility mode for lazy imports Add documentation for the __lazy_modules__ module attribute in two places: - Doc/reference/simple_stmts.rst: new subsection under the lazy imports section explaining the compatibility mode, its semantics (module-scope only, overridden by -X lazy_imports=none), and a usage example. - Doc/reference/datamodel.rst: new module.__lazy_modules__ attribute entry with a cross-reference to the detailed explanation in simple_stmts. Also extend the What's New in Python 3.15 lazy-imports entry with a short paragraph and example demonstrating __lazy_modules__. --- Doc/reference/datamodel.rst | 14 +++++++ Doc/reference/simple_stmts.rst | 40 +++++++++++++++++++ Doc/whatsnew/3.15.rst | 11 +++++ ...-04-15-00-06-40.gh-issue-148588.gC7AEZ.rst | 3 ++ 4 files changed, 68 insertions(+) create mode 100644 Misc/NEWS.d/next/Documentation/2026-04-15-00-06-40.gh-issue-148588.gC7AEZ.rst diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 1e53c0e0e6f971..c557a65f0c5cf2 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -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: @@ -1121,6 +1122,19 @@ the following writable attributes: .. versionadded:: 3.14 +.. attribute:: module.__lazy_modules__ + + An optional sequence of absolute module name strings. When defined at + module scope, any regular :keyword:`import` statement in that module whose + target module name appears in this sequence is treated as a + :ref:`lazy import `, 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 ^^^^^^^^^^^^^^^^^^^ diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 9b84c2e9ac7017..0a915ea2630c56 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -920,6 +920,46 @@ See :pep:`810` for the full specification of lazy imports. .. versionadded:: 3.15 +.. _lazy-modules-compat: + +Compatibility mode 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 +sequence of absolute 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 — useful when migrating existing +code or when the imports are generated programmatically:: + + __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 absolute module names. + +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 diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index c754b634ecccfa..56bfa60cb01161 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -182,6 +182,17 @@ 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 +migrating a large existing codebase — a module can define +:attr:`~module.__lazy_modules__` as a sequence of absolute 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`.) diff --git a/Misc/NEWS.d/next/Documentation/2026-04-15-00-06-40.gh-issue-148588.gC7AEZ.rst b/Misc/NEWS.d/next/Documentation/2026-04-15-00-06-40.gh-issue-148588.gC7AEZ.rst new file mode 100644 index 00000000000000..fc2478c3caf110 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2026-04-15-00-06-40.gh-issue-148588.gC7AEZ.rst @@ -0,0 +1,3 @@ +Document :attr:`module.__lazy_modules__`, the compatibility-mode mechanism +for opting specific imports into lazy loading without the :keyword:`lazy` +keyword, in the language reference and the "What's New in Python 3.15" page. From 8891a2563f1e1886dc6ea0e999f92a871313b556 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 02:15:59 +0200 Subject: [PATCH 02/14] gh-148588: Add cross-ref link to __lazy_modules__ in NEWS, drop docs entry Replace bare __lazy_modules__ in the 3.15.0a8 NEWS entry with :attr:`module.__lazy_modules__` to link to the new attribute documentation. Remove the separate docs NEWS entry; the archive entry serves as the record. --- Misc/NEWS.d/3.15.0a8.rst | 2 +- .../2026-04-15-00-06-40.gh-issue-148588.gC7AEZ.rst | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 Misc/NEWS.d/next/Documentation/2026-04-15-00-06-40.gh-issue-148588.gC7AEZ.rst diff --git a/Misc/NEWS.d/3.15.0a8.rst b/Misc/NEWS.d/3.15.0a8.rst index ed37988f6ab548..c78f9eee3a1f6b 100644 --- a/Misc/NEWS.d/3.15.0a8.rst +++ b/Misc/NEWS.d/3.15.0a8.rst @@ -186,7 +186,7 @@ dealing with contradictions in ``make_bottom``. .. section: Core and Builtins Ensure ``-X lazy_imports=none``` and ``PYTHON_LAZY_IMPORTS=none``` override -``__lazy_modules__``. Patch by Hugo van Kemenade. +:attr:`module.__lazy_modules__`. Patch by Hugo van Kemenade. .. diff --git a/Misc/NEWS.d/next/Documentation/2026-04-15-00-06-40.gh-issue-148588.gC7AEZ.rst b/Misc/NEWS.d/next/Documentation/2026-04-15-00-06-40.gh-issue-148588.gC7AEZ.rst deleted file mode 100644 index fc2478c3caf110..00000000000000 --- a/Misc/NEWS.d/next/Documentation/2026-04-15-00-06-40.gh-issue-148588.gC7AEZ.rst +++ /dev/null @@ -1,3 +0,0 @@ -Document :attr:`module.__lazy_modules__`, the compatibility-mode mechanism -for opting specific imports into lazy loading without the :keyword:`lazy` -keyword, in the language reference and the "What's New in Python 3.15" page. From f4a83667031345aea25c1aa00242b41d2dc94935 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 02:17:26 +0200 Subject: [PATCH 03/14] gh-148588: Fix __lazy_modules__ use-case description Replace 'migrating a large existing codebase' with the more accurate use case: supporting Python <3.15 while leveraging lazy imports on 3.15+. --- Doc/reference/simple_stmts.rst | 5 +++-- Doc/whatsnew/3.15.rst | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 0a915ea2630c56..5a8605200af369 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -937,8 +937,9 @@ sequence of absolute module name strings. Any regular (non-``lazy``) :keyword:`lazy` keyword had been used. This provides a way to enable lazy loading for specific dependencies without -changing individual ``import`` statements — useful when migrating existing -code or when the imports are generated programmatically:: +changing individual ``import`` statements — useful when supporting Python +versions older than 3.15 while leveraging lazy imports on 3.15+, or when +the imports are generated programmatically:: __lazy_modules__ = ["json", "pathlib"] diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 56bfa60cb01161..2cdedc714ff0c0 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -183,7 +183,8 @@ function, class body, or ``try``/``except``/``finally`` block raises a raise :exc:`SyntaxError`). For code that cannot use the ``lazy`` keyword directly — for example, when -migrating a large existing codebase — a module can define +supporting Python versions older than 3.15 while still leveraging lazy +imports on 3.15+ — a module can define :attr:`~module.__lazy_modules__` as a sequence of absolute module name strings. Regular ``import`` statements for those modules are then treated as lazy, with the same semantics as the ``lazy`` keyword:: From a65ba2132beb811f7bd4c7afbbacae6079412987 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 02:22:31 +0200 Subject: [PATCH 04/14] gh-148588: Remove em dashes from __lazy_modules__ docs --- Doc/reference/simple_stmts.rst | 6 +++--- Doc/whatsnew/3.15.rst | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 5a8605200af369..c114eea0f32631 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -937,9 +937,9 @@ sequence of absolute module name strings. Any regular (non-``lazy``) :keyword:`lazy` keyword had been used. This provides a way to enable lazy loading for specific dependencies without -changing individual ``import`` statements — useful when supporting Python -versions older than 3.15 while leveraging lazy imports on 3.15+, or when -the imports are generated programmatically:: +changing individual ``import`` statements. This is useful when supporting +Python versions older than 3.15 while leveraging lazy imports on 3.15+, or +when the imports are generated programmatically:: __lazy_modules__ = ["json", "pathlib"] diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 2cdedc714ff0c0..33d049fa83fe11 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -182,9 +182,9 @@ 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 +For code that cannot use the ``lazy`` keyword directly (for example, when supporting Python versions older than 3.15 while still leveraging lazy -imports on 3.15+ — a module can define +imports on 3.15+), a module can define :attr:`~module.__lazy_modules__` as a sequence of absolute module name strings. Regular ``import`` statements for those modules are then treated as lazy, with the same semantics as the ``lazy`` keyword:: From c99c1a412173fbaf1f0a6723128fe7c07b4d96c5 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 02:23:21 +0200 Subject: [PATCH 05/14] gh-148588: Drop 'generated programmatically' aside from __lazy_modules__ docs --- Doc/reference/simple_stmts.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index c114eea0f32631..6d966954468e58 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -938,8 +938,7 @@ sequence of absolute module name strings. Any regular (non-``lazy``) 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 leveraging lazy imports on 3.15+, or -when the imports are generated programmatically:: +Python versions older than 3.15 while leveraging lazy imports on 3.15+:: __lazy_modules__ = ["json", "pathlib"] From 907366e4a20ad4d494ca5f6ef0f56386595ff79d Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 02:26:46 +0200 Subject: [PATCH 06/14] gh-148588: Use :term:`iterable` not 'sequence' for __lazy_modules__ --- Doc/reference/datamodel.rst | 6 +++--- Doc/reference/simple_stmts.rst | 4 ++-- Doc/whatsnew/3.15.rst | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index c557a65f0c5cf2..73c2240dcffb5d 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1124,9 +1124,9 @@ the following writable attributes: .. attribute:: module.__lazy_modules__ - An optional sequence of absolute module name strings. When defined at - module scope, any regular :keyword:`import` statement in that module whose - target module name appears in this sequence is treated as a + An optional :term:`iterable` of absolute module name strings. When defined + at module scope, any regular :keyword:`import` statement in that module whose + target module name appears in this iterable is treated as a :ref:`lazy import `, as if the :keyword:`lazy` keyword had been used. Imports inside functions, class bodies, or :keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are unaffected. diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 6d966954468e58..cda9e6f43f0267 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -930,8 +930,8 @@ Compatibility mode via ``__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 -sequence of absolute module name strings. Any regular (non-``lazy``) +:attr:`~module.__lazy_modules__` variable. When present, it must be an +:term:`iterable` of absolute 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. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 33d049fa83fe11..a577ae1fde7b69 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -185,8 +185,8 @@ raise :exc:`SyntaxError`). For code that cannot use the ``lazy`` keyword directly (for example, when supporting Python versions older than 3.15 while still leveraging lazy imports on 3.15+), a module can define -:attr:`~module.__lazy_modules__` as a sequence of absolute module name -strings. Regular ``import`` statements for those modules are then treated +:attr:`~module.__lazy_modules__` as an :term:`iterable` of absolute 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"] From 2005434566124df79439833c092f143a79e0a5eb Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 02:34:21 +0200 Subject: [PATCH 07/14] gh-148588: Use 'fully qualified module name' consistently --- Doc/reference/datamodel.rst | 2 +- Doc/reference/simple_stmts.rst | 4 ++-- Doc/whatsnew/3.15.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 73c2240dcffb5d..2cc8699f5d6bf4 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1124,7 +1124,7 @@ the following writable attributes: .. attribute:: module.__lazy_modules__ - An optional :term:`iterable` of absolute module name strings. When defined + An optional :term:`iterable` 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 iterable is treated as a :ref:`lazy import `, as if the :keyword:`lazy` keyword had diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index cda9e6f43f0267..e521ac8b21edf2 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -931,7 +931,7 @@ Compatibility mode via ``__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 an -:term:`iterable` of absolute module name strings. Any regular (non-``lazy``) +:term:`iterable` 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. @@ -948,7 +948,7 @@ Python versions older than 3.15 while leveraging lazy imports on 3.15+:: import pathlib # loaded lazily Relative imports are resolved to their absolute name before the lookup, so -the sequence must always contain absolute module names. +the sequence must always contain fully qualified module names. Imports inside functions, class bodies, or :keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are always eager, diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index a577ae1fde7b69..23bfc319ab76e4 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -185,7 +185,7 @@ raise :exc:`SyntaxError`). For code that cannot use the ``lazy`` keyword directly (for example, when supporting Python versions older than 3.15 while still leveraging lazy imports on 3.15+), a module can define -:attr:`~module.__lazy_modules__` as an :term:`iterable` of absolute module +:attr:`~module.__lazy_modules__` as an :term:`iterable` 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:: From e86de6ba07476e782bee2251efd4f259fd770d84 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 02:50:05 +0200 Subject: [PATCH 08/14] gh-148588: Add relative import example to __lazy_modules__ docs --- Doc/reference/simple_stmts.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index e521ac8b21edf2..7b3c0572312a1f 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -948,7 +948,13 @@ Python versions older than 3.15 while leveraging lazy imports on 3.15+:: 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. +the sequence must always contain fully qualified module names:: + + # In mypackage/mymodule.py + __lazy_modules__ = ["mypackage"] + + from . import utils # loaded lazily: resolves to mypackage + import json # loaded eagerly (not in __lazy_modules__) Imports inside functions, class bodies, or :keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are always eager, From b6e14eb0e2b54a743755af47ccdb867fe6f7408a Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 02:50:48 +0200 Subject: [PATCH 09/14] gh-148588: Drop 'optional' from __lazy_modules__ iterable description --- Doc/reference/datamodel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 2cc8699f5d6bf4..8098337a7881e4 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1124,7 +1124,7 @@ the following writable attributes: .. attribute:: module.__lazy_modules__ - An optional :term:`iterable` of fully qualified module name strings. When defined + An :term:`iterable` 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 iterable is treated as a :ref:`lazy import `, as if the :keyword:`lazy` keyword had From 650795952efa6cc0ca4a046ac5c4de892a5636b6 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 02:59:53 +0200 Subject: [PATCH 10/14] gh-148588: Clarify __lazy_modules__ with relative import example --- Doc/reference/simple_stmts.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 7b3c0572312a1f..8f2f9e2305bb40 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -948,13 +948,17 @@ Python versions older than 3.15 while leveraging lazy imports on 3.15+:: 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:: +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"] + __lazy_modules__ = ["mypackage", "mypackage.sub.utils"] - from . import utils # loaded lazily: resolves to mypackage - import json # loaded eagerly (not in __lazy_modules__) + 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, From e9c0d3b48b7b6a6c4b7b0cac38157acbc867a0ec Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 03:29:56 +0200 Subject: [PATCH 11/14] gh-148588: Use 'container' not 'iterable' for __lazy_modules__ --- Doc/reference/datamodel.rst | 4 ++-- Doc/reference/simple_stmts.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 8098337a7881e4..a92ee834c71e47 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1124,9 +1124,9 @@ the following writable attributes: .. attribute:: module.__lazy_modules__ - An :term:`iterable` of fully qualified module name strings. When defined + A container 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 iterable is treated as a + target module name appears in this container is treated as a :ref:`lazy import `, as if the :keyword:`lazy` keyword had been used. Imports inside functions, class bodies, or :keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are unaffected. diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 8f2f9e2305bb40..8f761d775faab2 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -930,8 +930,8 @@ Compatibility mode via ``__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 an -:term:`iterable` of fully qualified module name strings. Any regular (non-``lazy``) +: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. From f24d587765b8bf41b67eac667cd233a3f957b4c4 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 03:31:22 +0200 Subject: [PATCH 12/14] gh-148588: Clarify __lazy_modules__ must be a container (__contains__), not any iterable --- Doc/reference/datamodel.rst | 3 ++- Doc/reference/simple_stmts.rst | 3 ++- Doc/whatsnew/3.15.rst | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index a92ee834c71e47..2089984404cec6 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1124,7 +1124,8 @@ the following writable attributes: .. attribute:: module.__lazy_modules__ - A container of fully qualified module name strings. When defined + 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 `, as if the :keyword:`lazy` keyword had diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 8f761d775faab2..e4894a723d528d 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -931,7 +931,8 @@ Compatibility mode via ``__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``) +container (an object implementing :meth:`~object.__contains__`) 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. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 23bfc319ab76e4..207c1d1d30c3e9 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -185,8 +185,8 @@ raise :exc:`SyntaxError`). For code that cannot use the ``lazy`` keyword directly (for example, when supporting Python versions older than 3.15 while still leveraging lazy imports on 3.15+), a module can define -:attr:`~module.__lazy_modules__` as an :term:`iterable` of fully qualified module -name strings. Regular ``import`` statements for those modules are then treated +:attr:`~module.__lazy_modules__` as a container (an object implementing +:meth:`~object.__contains__`) 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"] From 5f73065b83da1a057935968e2ba2fcaad4277e59 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Wed, 15 Apr 2026 03:32:11 +0200 Subject: [PATCH 13/14] gh-148588: Drop redundant __contains__ clarification from simple_stmts and whatsnew --- Doc/reference/simple_stmts.rst | 3 +-- Doc/whatsnew/3.15.rst | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index e4894a723d528d..8f761d775faab2 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -931,8 +931,7 @@ Compatibility mode via ``__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 (an object implementing :meth:`~object.__contains__`) of fully -qualified module name strings. Any regular (non-``lazy``) +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. diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 207c1d1d30c3e9..4b00117e666ab6 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -185,8 +185,8 @@ raise :exc:`SyntaxError`). For code that cannot use the ``lazy`` keyword directly (for example, when supporting Python versions older than 3.15 while still leveraging lazy imports on 3.15+), a module can define -:attr:`~module.__lazy_modules__` as a container (an object implementing -:meth:`~object.__contains__`) of fully qualified module name strings. Regular ``import`` statements for those modules are then treated +: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"] From 7900e3236ce24f9a79e2da3455e8b887e1d4ee13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Wed, 15 Apr 2026 18:06:20 +0200 Subject: [PATCH 14/14] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/reference/simple_stmts.rst | 6 +++--- Doc/whatsnew/3.15.rst | 2 +- Misc/NEWS.d/3.15.0a8.rst | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 8f761d775faab2..3c0cb6c7ee52ff 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -922,8 +922,8 @@ See :pep:`810` for the full specification of lazy imports. .. _lazy-modules-compat: -Compatibility mode via ``__lazy_modules__`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Compatibility via ``__lazy_modules__`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. index:: single: __lazy_modules__ @@ -938,7 +938,7 @@ container of fully qualified module name strings. Any regular (non-``lazy``) 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 leveraging lazy imports on 3.15+:: +Python versions older than 3.15 while using lazy imports in 3.15+:: __lazy_modules__ = ["json", "pathlib"] diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 4b00117e666ab6..58f0755c2c6e92 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -183,7 +183,7 @@ function, class body, or ``try``/``except``/``finally`` block raises a raise :exc:`SyntaxError`). For code that cannot use the ``lazy`` keyword directly (for example, when -supporting Python versions older than 3.15 while still leveraging lazy +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 diff --git a/Misc/NEWS.d/3.15.0a8.rst b/Misc/NEWS.d/3.15.0a8.rst index c78f9eee3a1f6b..ff7930aeb292d6 100644 --- a/Misc/NEWS.d/3.15.0a8.rst +++ b/Misc/NEWS.d/3.15.0a8.rst @@ -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 -:attr:`module.__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. ..