Skip to content

Commit 46d5106

Browse files
pablogsalYhg1sbrittanyreyDinoV
authored
gh-142349: Implement PEP 810 - Explicit lazy imports (#142351)
Co-authored-by: T. Wouters <twouters@meta.com > Co-authored-by: Brittany Reynoso <breynoso@meta.com> Co-authored-by: Dino Viehland <dinoviehland@meta.com>
1 parent cac0c98 commit 46d5106

File tree

138 files changed

+5126
-197
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

138 files changed

+5126
-197
lines changed

Doc/c-api/exceptions.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,8 @@ Exception types
11191119
* :exc:`FloatingPointError`
11201120
* * .. c:var:: PyObject *PyExc_GeneratorExit
11211121
* :exc:`GeneratorExit`
1122+
* * .. c:var:: PyObject *PyExc_ImportCycleError
1123+
* :exc:`ImportCycleError`
11221124
* * .. c:var:: PyObject *PyExc_ImportError
11231125
* :exc:`ImportError`
11241126
* * .. c:var:: PyObject *PyExc_IndentationError

Doc/c-api/import.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,58 @@ Importing Modules
346346
347347
.. versionadded:: 3.14
348348
349+
.. c:function:: PyImport_LazyImportsMode PyImport_GetLazyImportsMode()
350+
351+
Gets the current lazy imports mode.
352+
353+
.. versionadded:: next
354+
355+
.. c:function:: PyObject* PyImport_GetLazyImportsFilter()
356+
357+
Return a :term:`strong reference` to the current lazy imports filter,
358+
or ``NULL`` if none exists. This function always succeeds.
359+
360+
.. versionadded:: next
361+
362+
.. c:function:: int PyImport_SetLazyImportsMode(PyImport_LazyImportsMode mode)
363+
364+
Similar to :c:func:`PyImport_ImportModuleAttr`, but names are UTF-8 encoded
365+
strings instead of Python :class:`str` objects.
366+
367+
This function always returns ``0``.
368+
369+
.. versionadded:: next
370+
371+
.. c:function:: int PyImport_SetLazyImportsFilter(PyObject *filter)
372+
373+
Sets the current lazy imports filter. The *filter* should be a callable that
374+
will receive ``(importing_module_name, imported_module_name, [fromlist])``
375+
when an import can potentially be lazy and that must return ``True`` if
376+
the import should be lazy and ``False`` otherwise.
377+
378+
Return ``0`` on success and ``-1`` with an exception set otherwise.
379+
380+
.. versionadded:: next
381+
382+
.. c:type:: PyImport_LazyImportsMode
383+
384+
Enumeration of possible lazy import modes.
385+
386+
.. c:enumerator:: PyImport_LAZY_NORMAL
387+
388+
Respect the ``lazy`` keyword in source code. This is the default mode.
389+
390+
.. c:enumerator:: PyImport_LAZY_ALL
391+
392+
Make all imports lazy by default.
393+
394+
.. c:enumerator:: PyImport_LAZY_NONE
395+
396+
Disable lazy imports entirely. Even explicit ``lazy`` statements become
397+
eager imports.
398+
399+
.. versionadded:: next
400+
349401
.. c:function:: PyObject* PyImport_CreateModuleFromInitfunc(PyObject *spec, PyObject* (*initfunc)(void))
350402
351403
This function is a building block that enables embedders to implement

Doc/library/ast.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,7 +1114,8 @@ Imports
11141114
names=[
11151115
alias(name='x'),
11161116
alias(name='y'),
1117-
alias(name='z')])])
1117+
alias(name='z')],
1118+
is_lazy=0)])
11181119

11191120

11201121
.. class:: ImportFrom(module, names, level)
@@ -1135,7 +1136,8 @@ Imports
11351136
alias(name='x'),
11361137
alias(name='y'),
11371138
alias(name='z')],
1138-
level=0)])
1139+
level=0,
1140+
is_lazy=0)])
11391141

11401142

11411143
.. class:: alias(name, asname)
@@ -1153,7 +1155,8 @@ Imports
11531155
names=[
11541156
alias(name='a', asname='b'),
11551157
alias(name='c')],
1156-
level=2)])
1158+
level=2,
1159+
is_lazy=0)])
11571160

11581161
Control flow
11591162
^^^^^^^^^^^^

Doc/library/exceptions.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@ The following exceptions are the exceptions that are usually raised.
266266

267267
.. versionadded:: 3.6
268268

269+
.. exception:: ImportCycleError
270+
271+
A subclass of :exc:`ImportError` which is raised when a lazy import fails
272+
because it (directly or indirectly) tries to import itself.
273+
274+
.. versionadded:: next
269275

270276
.. exception:: IndexError
271277

Doc/library/sys.rst

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,35 @@ always available. Unless explicitly noted otherwise, all variables are read-only
911911

912912
.. versionadded:: 3.11
913913

914+
915+
.. function:: get_lazy_imports()
916+
917+
Returns the current lazy imports mode as a string.
918+
919+
* ``"normal"``: Only imports explicitly marked with the ``lazy`` keyword
920+
are lazy
921+
* ``"all"``: All top-level imports are potentially lazy
922+
* ``"none"``: All lazy imports are suppressed (even explicitly marked
923+
ones)
924+
925+
See also :func:`set_lazy_imports` and :pep:`810`.
926+
927+
.. versionadded:: next
928+
929+
930+
.. function:: get_lazy_imports_filter()
931+
932+
Returns the current lazy imports filter function, or ``None`` if no
933+
filter is set.
934+
935+
The filter function is called for every potentially lazy import to
936+
determine whether it should actually be lazy. See
937+
:func:`set_lazy_imports_filter` for details on the filter function
938+
signature.
939+
940+
.. versionadded:: next
941+
942+
914943
.. function:: getrefcount(object)
915944

916945
Return the reference count of the *object*. The count returned is generally one
@@ -1719,6 +1748,61 @@ always available. Unless explicitly noted otherwise, all variables are read-only
17191748

17201749
.. versionadded:: 3.11
17211750

1751+
1752+
.. function:: set_lazy_imports(mode)
1753+
1754+
Sets the global lazy imports mode. The *mode* parameter must be one of
1755+
the following strings:
1756+
1757+
* ``"normal"``: Only imports explicitly marked with the ``lazy`` keyword
1758+
are lazy
1759+
* ``"all"``: All top-level imports become potentially lazy
1760+
* ``"none"``: All lazy imports are suppressed (even explicitly marked
1761+
ones)
1762+
1763+
This function is intended for advanced users who need to control lazy
1764+
imports across their entire application. Library developers should
1765+
generally not use this function as it affects the runtime execution of
1766+
applications.
1767+
1768+
In addition to the mode, lazy imports can be controlled via the filter
1769+
provided by :func:`set_lazy_imports_filter`.
1770+
1771+
See also :func:`get_lazy_imports` and :pep:`810`.
1772+
1773+
.. versionadded:: next
1774+
1775+
1776+
.. function:: set_lazy_imports_filter(filter)
1777+
1778+
Sets the lazy imports filter callback. The *filter* parameter must be a
1779+
callable or ``None`` to clear the filter.
1780+
1781+
The filter function is called for every potentially lazy import to
1782+
determine whether it should actually be lazy. It must have the following
1783+
signature::
1784+
1785+
def filter(importing_module: str, imported_module: str,
1786+
fromlist: tuple[str, ...] | None) -> bool
1787+
1788+
Where:
1789+
1790+
* *importing_module* is the name of the module doing the import
1791+
* *imported_module* is the name of the module being imported
1792+
* *fromlist* is the tuple of names being imported (for ``from ... import``
1793+
statements), or ``None`` for regular imports
1794+
1795+
The filter should return ``True`` to allow the import to be lazy, or
1796+
``False`` to force an eager import.
1797+
1798+
This is an advanced feature intended for specialized users who need
1799+
fine-grained control over lazy import behavior.
1800+
1801+
See also :func:`get_lazy_imports_filter` and :pep:`810`.
1802+
1803+
.. versionadded:: next
1804+
1805+
17221806
.. function:: setprofile(profilefunc)
17231807

17241808
.. index::

Doc/library/types.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,18 @@ Standard names are defined for the following types:
343343
.. seealso:: :pep:`667`
344344

345345

346+
.. data:: LazyImportType
347+
348+
The type of lazy import proxy objects. These objects are created when a
349+
module is lazily imported and serve as placeholders until the module is
350+
actually accessed. This type can be used to detect lazy imports
351+
programmatically.
352+
353+
.. versionadded:: next
354+
355+
.. seealso:: :pep:`810`
356+
357+
346358
.. data:: GetSetDescriptorType
347359

348360
The type of objects defined in extension modules with ``PyGetSetDef``, such

Doc/reference/lexical_analysis.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ Some names are only reserved under specific contexts. These are known as
457457

458458
- ``match``, ``case``, and ``_``, when used in the :keyword:`match` statement.
459459
- ``type``, when used in the :keyword:`type` statement.
460+
- ``lazy``, when used before an :keyword:`import` statement.
460461

461462
These syntactically act as keywords in their specific contexts,
462463
but this distinction is done at the parser level, not when tokenizing.
@@ -468,6 +469,9 @@ identifier names.
468469
.. versionchanged:: 3.12
469470
``type`` is now a soft keyword.
470471

472+
.. versionchanged:: next
473+
``lazy`` is now a soft keyword.
474+
471475
.. index::
472476
single: _, identifiers
473477
single: __, identifiers

Doc/reference/simple_stmts.rst

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -743,14 +743,15 @@ The :keyword:`!import` statement
743743
pair: name; binding
744744
pair: keyword; from
745745
pair: keyword; as
746+
pair: keyword; lazy
746747
pair: exception; ImportError
747748
single: , (comma); import statement
748749

749750
.. productionlist:: python-grammar
750-
import_stmt: "import" `module` ["as" `identifier`] ("," `module` ["as" `identifier`])*
751-
: | "from" `relative_module` "import" `identifier` ["as" `identifier`]
751+
import_stmt: ["lazy"] "import" `module` ["as" `identifier`] ("," `module` ["as" `identifier`])*
752+
: | ["lazy"] "from" `relative_module` "import" `identifier` ["as" `identifier`]
752753
: ("," `identifier` ["as" `identifier`])*
753-
: | "from" `relative_module` "import" "(" `identifier` ["as" `identifier`]
754+
: | ["lazy"] "from" `relative_module` "import" "(" `identifier` ["as" `identifier`]
754755
: ("," `identifier` ["as" `identifier`])* [","] ")"
755756
: | "from" `relative_module` "import" "*"
756757
module: (`identifier` ".")* `identifier`
@@ -869,6 +870,56 @@ determine dynamically the modules to be loaded.
869870

870871
.. _normalization form: https://www.unicode.org/reports/tr15/#Norm_Forms
871872

873+
.. _lazy-imports:
874+
.. _lazy:
875+
876+
Lazy imports
877+
------------
878+
879+
.. index::
880+
pair: lazy; import
881+
single: lazy import
882+
883+
The :keyword:`lazy` keyword is a :ref:`soft keyword <soft-keywords>` that
884+
only has special meaning when it appears immediately before an
885+
:keyword:`import` or :keyword:`from` statement. When an import statement is
886+
preceded by the :keyword:`lazy` keyword, the import becomes *lazy*: the
887+
module is not loaded immediately at the import statement. Instead, a lazy
888+
proxy object is created and bound to the name. The actual module is loaded
889+
on first use of that name.
890+
891+
Lazy imports are only permitted at module scope. Using :keyword:`lazy`
892+
inside a function, class body, or
893+
:keyword:`try`/:keyword:`except`/:keyword:`finally` block raises a
894+
:exc:`SyntaxError`. Star imports cannot be lazy (``lazy from module import
895+
*`` is a syntax error), and :ref:`future statements <future>` cannot be
896+
lazy.
897+
898+
When using ``lazy from ... import``, each imported name is bound to a lazy
899+
proxy object. The first access to any of these names triggers loading of the
900+
entire module and resolves only that specific name to its actual value.
901+
Other names remain as lazy proxies until they are accessed.
902+
903+
Example::
904+
905+
lazy import json
906+
import sys
907+
908+
print('json' in sys.modules) # False - json module not yet loaded
909+
910+
# First use triggers loading
911+
result = json.dumps({"hello": "world"})
912+
913+
print('json' in sys.modules) # True - now loaded
914+
915+
If an error occurs during module loading (such as :exc:`ImportError` or
916+
:exc:`SyntaxError`), it is raised at the point where the lazy import is first
917+
used, not at the import statement itself.
918+
919+
See :pep:`810` for the full specification of lazy imports.
920+
921+
.. versionadded:: next
922+
872923
.. _future:
873924

874925
Future statements

Doc/using/cmdline.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,14 @@ Miscellaneous options
694694

695695
.. versionadded:: 3.14
696696

697+
* :samp:`-X lazy_imports={all,none,normal}` controls lazy import behavior.
698+
``all`` makes all imports lazy by default, ``none`` disables lazy imports
699+
entirely (even explicit ``lazy`` statements become eager), and ``normal``
700+
(the default) respects the ``lazy`` keyword in source code.
701+
See also :envvar:`PYTHON_LAZY_IMPORTS`.
702+
703+
.. versionadded:: next
704+
697705
It also allows passing arbitrary values and retrieving them through the
698706
:data:`sys._xoptions` dictionary.
699707

@@ -1360,6 +1368,17 @@ conflict.
13601368

13611369
.. versionadded:: 3.14
13621370

1371+
.. envvar:: PYTHON_LAZY_IMPORTS
1372+
1373+
Controls lazy import behavior. Accepts three values: ``all`` makes all
1374+
imports lazy by default, ``none`` disables lazy imports entirely (even
1375+
explicit ``lazy`` statements become eager), and ``normal`` (the default)
1376+
respects the ``lazy`` keyword in source code.
1377+
1378+
See also the :option:`-X lazy_imports <-X>` command-line option.
1379+
1380+
.. versionadded:: next
1381+
13631382
Debug-mode variables
13641383
~~~~~~~~~~~~~~~~~~~~
13651384

0 commit comments

Comments
 (0)