Skip to content

Commit 33dc19e

Browse files
Merge branch 'lazy' into lazy-future-error
2 parents e777866 + 952ac21 commit 33dc19e

File tree

13 files changed

+96
-27
lines changed

13 files changed

+96
-27
lines changed

Doc/c-api/import.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -353,19 +353,23 @@ Importing Modules
353353
354354
.. versionadded:: next
355355
356-
.. c:function:: PyObject* PyImport_SetLazyImportsMode(PyImport_LazyImportsMode mode)
356+
.. c:function:: int PyImport_SetLazyImportsMode(PyImport_LazyImportsMode mode)
357357
358358
Similar to :c:func:`PyImport_ImportModuleAttr`, but names are UTF-8 encoded
359359
strings instead of Python :class:`str` objects.
360360
361+
This function always returns ``0``.
362+
361363
.. versionadded:: next
362364
363-
.. c:function:: PyObject* PyImport_SetLazyImportsFilter(PyObject *filter)
365+
.. c:function:: int PyImport_SetLazyImportsFilter(PyObject *filter)
366+
367+
Sets the current lazy imports filter. The *filter* should be a callable that
368+
will receive ``(importing_module_name, imported_module_name, [fromlist])``
369+
when an import can potentially be lazy and that must return ``True`` if
370+
the import should be lazy and ``False`` otherwise.
364371
365-
Sets the current lazy imports filter. The function should be a callable that
366-
will receive (importing_module_name, imported_module_name, [fromlist]) when
367-
an import can potentially be lazy. Returns ``True`` if the import should be lazy
368-
or ``False`` otherwise.
372+
Return ``0`` on success and ``-1`` with an exception set otherwise.
369373
370374
.. versionadded:: next
371375

Doc/library/types.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ Standard names are defined for the following types:
350350
actually accessed. This type can be used to detect lazy imports
351351
programmatically.
352352

353-
.. versionadded:: 3.15
353+
.. versionadded:: next
354354

355355
.. seealso:: :pep:`810`
356356

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
@@ -748,14 +748,15 @@ The :keyword:`!import` statement
748748
pair: name; binding
749749
pair: keyword; from
750750
pair: keyword; as
751+
pair: keyword; lazy
751752
pair: exception; ImportError
752753
single: , (comma); import statement
753754

754755
.. productionlist:: python-grammar
755-
import_stmt: "import" `module` ["as" `identifier`] ("," `module` ["as" `identifier`])*
756-
: | "from" `relative_module` "import" `identifier` ["as" `identifier`]
756+
import_stmt: ["lazy"] "import" `module` ["as" `identifier`] ("," `module` ["as" `identifier`])*
757+
: | ["lazy"] "from" `relative_module` "import" `identifier` ["as" `identifier`]
757758
: ("," `identifier` ["as" `identifier`])*
758-
: | "from" `relative_module` "import" "(" `identifier` ["as" `identifier`]
759+
: | ["lazy"] "from" `relative_module` "import" "(" `identifier` ["as" `identifier`]
759760
: ("," `identifier` ["as" `identifier`])* [","] ")"
760761
: | "from" `relative_module` "import" "*"
761762
module: (`identifier` ".")* `identifier`
@@ -870,6 +871,56 @@ determine dynamically the modules to be loaded.
870871

871872
.. audit-event:: import module,filename,sys.path,sys.meta_path,sys.path_hooks import
872873

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

875926
Future statements

Doc/using/cmdline.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ Miscellaneous options
700700
(the default) respects the ``lazy`` keyword in source code.
701701
See also :envvar:`PYTHON_LAZY_IMPORTS`.
702702

703-
.. versionadded:: 3.15
703+
.. versionadded:: next
704704

705705
It also allows passing arbitrary values and retrieving them through the
706706
:data:`sys._xoptions` dictionary.
@@ -1356,7 +1356,7 @@ conflict.
13561356

13571357
See also the :option:`-X lazy_imports <-X>` command-line option.
13581358

1359-
.. versionadded:: 3.15
1359+
.. versionadded:: next
13601360

13611361
Debug-mode variables
13621362
~~~~~~~~~~~~~~~~~~~~

Doc/whatsnew/3.15.rst

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,17 @@ runtime.
139139

140140
For more selective control, :func:`sys.set_lazy_imports_filter` accepts a callable
141141
that determines whether a specific module should be loaded lazily. The filter
142-
receives the fully-qualified module name and returns a boolean. This allows
143-
patterns like making only your own application's modules lazy while keeping
144-
third-party dependencies eager:
142+
receives three arguments: the importing module's name (or ``None``), the imported
143+
module's name, and the fromlist (or ``None`` for regular imports). It should
144+
return ``True`` to allow the import to be lazy, or ``False`` to force eager loading.
145+
This allows patterns like making only your own application's modules lazy while
146+
keeping third-party dependencies eager:
145147

146148
.. code-block:: python
147149
148150
import sys
149151
150-
sys.set_lazy_imports_filter(lambda name: name.startswith("myapp."))
152+
sys.set_lazy_imports_filter(lambda importing, imported, fromlist: imported.startswith("myapp."))
151153
sys.set_lazy_imports("all")
152154
153155
import myapp.slow_module # lazy (matches filter)

Include/import.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ PyAPI_FUNC(int) PyImport_AppendInittab(
8888
PyObject* (*initfunc)(void)
8989
);
9090

91-
typedef enum {
91+
typedef enum {
9292
PyImport_LAZY_NORMAL,
9393
PyImport_LAZY_ALL,
9494
PyImport_LAZY_NONE,

Include/internal/pycore_magic_number.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ Known values:
287287
Python 3.15a1 3654 (Fix missing exception handlers in logical expression)
288288
Python 3.15a1 3655 (Fix miscompilation of some module-level annotations)
289289
Python 3.15a1 3656 (Add TRACE_RECORD instruction, for platforms with switch based interpreter)
290-
Python 3.15a1 3657 Lazy imports IMPORT_NAME opcode changes
290+
Python 3.15a3 3657 (Lazy imports IMPORT_NAME opcode changes)
291291
292292
293293
Python 3.16 will start with 3700

Lib/rlcompleter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def attr_matches(self, text):
192192

193193
if (isinstance(thisobject, types.ModuleType)
194194
and
195-
isinstance(thisobject.__dict__.get(word),types.LazyImportType)
195+
isinstance(thisobject.__dict__.get(word), types.LazyImportType)
196196
):
197197
value = thisobject.__dict__.get(word)
198198
else:

PC/python3dll.c

Lines changed: 0 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)