Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
1c691ea
Lazy imports grammar / AST changes
DinoV Sep 16, 2025
3b0d745
Add __lazy_import__, check sys.modules before import
DinoV Sep 18, 2025
9eef03c
Export lazy_import in imp module
DinoV Sep 18, 2025
de281fd
Add lazy import filter
DinoV Sep 19, 2025
41ab092
Add compatiblity mode
DinoV Sep 22, 2025
058bc6e
Flow import func through to lazy imports object and __lazy_import__
DinoV Sep 22, 2025
6d7c87a
Reify lazy objects when accessed via the module object
DinoV Sep 22, 2025
f992ee7
Import lazy.get
DinoV Sep 22, 2025
6a91132
Implement better error
pablogsal Sep 22, 2025
03a419a
Fix reference and global dict in spezializing LOAD_GLOBAL
pablogsal Sep 23, 2025
e0878be
Remove copyright
pablogsal Sep 23, 2025
f9880bf
More fixes
pablogsal Sep 23, 2025
f3f5795
Fix recursive lazy imports and error path in bytecodes.c
pablogsal Sep 23, 2025
44a3e46
fix offset in addr2line
pablogsal Sep 23, 2025
73de8d0
Add global flag
pablogsal Sep 25, 2025
07a633f
Draft: force * imports to be eager
pablogsal Sep 25, 2025
20b14d9
Syntax restrictions for lazy imports
pablogsal Sep 25, 2025
164423b
Fix submodules crash
pablogsal Sep 26, 2025
00e7800
Implement disabling imports in try/except and * imports, report error…
DinoV Sep 29, 2025
781eedb
Add PyExc_ImportCycleError and raise it when a cycle is detected
DinoV Sep 29, 2025
9be59ec
Publish lazy imported packages on parent
DinoV Sep 29, 2025
b179da2
Re-enable eagerly returning imported module
DinoV Sep 29, 2025
9078f57
Fix __lazy_modules__
pablogsal Oct 1, 2025
f67310c
Add sys.set_lazy_imports_filter
pablogsal Oct 1, 2025
39c33df
Simplify grammar
pablogsal Oct 2, 2025
c8c8838
Move __lazy_imports__ check into the interpreter
DinoV Oct 2, 2025
7c49405
Don't allow __lazy_imports__ to work in try/except
DinoV Oct 2, 2025
5d6026a
Make imports in with blocks syntax errors
pablogsal Oct 2, 2025
c0c0d80
Highlight lazy imports in the new REPL
johnslavik Oct 3, 2025
5ff0dd2
Merge pull request #2 from bswck/lazy-import-pyrepl-highlight
pablogsal Oct 3, 2025
214b254
Fix global membership in LOAD_NAME
pablogsal Oct 7, 2025
e5e9592
C was a mistake
pablogsal Oct 7, 2025
aa85f9d
dir() doesn't reify module
DinoV Oct 7, 2025
2799a4d
Merge pull request #7 from LazyImportsCabal/lazy_dir
pablogsal Oct 7, 2025
7d07ae1
Add test case for external usage
DinoV Oct 7, 2025
c63198c
Move eager check for from imports into import from
DinoV Oct 2, 2025
46b3b75
Merge pull request #8 from LazyImportsCabal/lazy_test
DinoV Oct 8, 2025
c5efb20
Expose LazyImportType in types module
DinoV Oct 8, 2025
0c246bc
__import__ is loaded at reification time
DinoV Oct 8, 2025
d9ad012
Fix leaks and LOAD_ATTR specialization
DinoV Oct 8, 2025
fe526b4
Merge pull request #9 from LazyImportsCabal/cleanup
pablogsal Oct 8, 2025
06b9110
Update sys module to conform with the PEP, add matching C API
DinoV Oct 9, 2025
ee77665
Merge pull request #10 from LazyImportsCabal/sys_c_api
DinoV Oct 9, 2025
a3ddde4
Fix specialization of load global
DinoV Oct 20, 2025
cdec6a6
Merge pull request #12 from DinoV/lazy_mt
pablogsal Oct 20, 2025
c3b4807
Fix bug in specialization and make reification atomic
pablogsal Oct 21, 2025
2e19765
Fix another race
pablogsal Oct 21, 2025
2af21a1
Merge pull request #14 from LazyImportsCabal/fix
DinoV Oct 22, 2025
34ea0a5
Make __dict__ not reify to match the PEP
DinoV Oct 23, 2025
5166d39
Allow try/except in with block
DinoV Oct 23, 2025
a0a9184
Merge pull request #16 from LazyImportsCabal/lazy_globals
pablogsal Oct 24, 2025
1f6518d
Grab builtins dict from module
DinoV Oct 25, 2025
90246cc
Merge pull request #17 from DinoV/lazy_builtins
DinoV Oct 25, 2025
824ac88
Fix eager imports in try/except in global mode
pablogsal Oct 25, 2025
c075f5b
Merge pull request #18 from LazyImportsCabal/bad_global
DinoV Oct 25, 2025
59110fc
Don't reify on REPL completion
pablogsal Oct 25, 2025
aeda7ac
Add tests for __lazy_import__
DinoV Oct 27, 2025
8d57aca
Merge remote-tracking branch 'origin/main' into HEAD
DinoV Nov 3, 2025
d8f95f7
Merge pull request #21 from LazyImportsCabal/lazy_rebase
DinoV Nov 5, 2025
b743eb0
Merge pull request #19 from DinoV/lazy_tests_dunder
pablogsal Nov 9, 2025
e6cb131
Implement more of PEP 810
pablogsal Dec 6, 2025
0409481
Add more tests
pablogsal Dec 6, 2025
617e9d1
Add more tests
pablogsal Dec 6, 2025
db151a5
Merge remote-tracking branch 'upstream/main' into lazy
pablogsal Dec 6, 2025
ea120fc
Regen stuff
pablogsal Dec 6, 2025
973a4fa
Ah yes....windows
pablogsal Dec 6, 2025
266fd4d
More windows stuff and news
pablogsal Dec 6, 2025
441602a
Updated versions
pablogsal Dec 6, 2025
e6633ff
Update Doc/whatsnew/3.15.rst
pablogsal Dec 6, 2025
2f642c8
Add support in IDLE colorizer
StanFromIreland Dec 6, 2025
ab07b14
Merge pull request #22 from StanFromIreland/lazy-idle
pablogsal Dec 6, 2025
76846fe
Address feedback
pablogsal Dec 6, 2025
0b36549
Fix tests
pablogsal Dec 6, 2025
971d395
Update Doc/whatsnew/3.15.rst
pablogsal Dec 6, 2025
0c019fd
Ruff fixes
pablogsal Dec 6, 2025
8e1b20a
JIT fixes
pablogsal Dec 6, 2025
70e5ce7
More jit fixes
pablogsal Dec 6, 2025
a39cd25
Fix smelly
pablogsal Dec 6, 2025
f70d4df
Fix smelly
pablogsal Dec 6, 2025
510c200
Fix repl not coloring 'lazy' after 'from'
StanFromIreland Dec 6, 2025
d58b0cf
Doc fixes
pablogsal Dec 6, 2025
8e4f292
brrrrrrrrrrrrrrrrrrrrrr
pablogsal Dec 6, 2025
4cd4322
Merge pull request #24 from StanFromIreland/lazy-color
pablogsal Dec 6, 2025
2d3681e
PeRfoRManCe
pablogsal Dec 6, 2025
4cc6905
PeRfoRManCe
pablogsal Dec 6, 2025
4f675fd
Amend return types for `PyImport_SetLazy*` functions
picnixz Dec 6, 2025
4c3477b
moar fixes much refleaks wow
pablogsal Dec 6, 2025
2f7d223
Merge pull request #25 from picnixz/patch-1
pablogsal Dec 6, 2025
0985e2a
Doc updates
pablogsal Dec 6, 2025
610af78
Address feedback
pablogsal Dec 6, 2025
917b92e
More doc fixes
pablogsal Dec 6, 2025
e777866
Make error for invalid `lazy from __future__ ...` import prettier
StanFromIreland Dec 6, 2025
952ac21
Regen limited ABI
pablogsal Dec 6, 2025
33dc19e
Merge branch 'lazy' into lazy-future-error
StanFromIreland Dec 6, 2025
80133a5
Fix error paths and edge cases
pablogsal Dec 6, 2025
b0adc30
Free threading fixes
pablogsal Dec 6, 2025
470b9e4
TSAN fixes
pablogsal Dec 6, 2025
41761e5
Update magic number
pablogsal Dec 6, 2025
74b3efe
Merge pull request #26 from StanFromIreland/lazy-future-error
pablogsal Dec 6, 2025
006ca9c
Regen frozenmain
pablogsal Dec 6, 2025
b5121b0
Fix WASI failure
pablogsal Dec 6, 2025
093f08b
document return value for `PyImport_GetLazyImportsFilter`
picnixz Dec 7, 2025
2a514d9
fix error path in `_PyImport_LoadLazyImportTstate`
picnixz Dec 7, 2025
74f35c4
fix UAFs in `register_lazy_on_parent`
picnixz Dec 7, 2025
3014816
fix error path in `_PyImport_LazyImportModuleLevelObject`
picnixz Dec 7, 2025
f96a99c
fix error path in `_imp__set_lazy_attributes_impl`
picnixz Dec 7, 2025
c4ed3c4
Various UAFs & cosmetic fixes in `Python/{bltinmodule,bytecode,ceval}…
picnixz Dec 7, 2025
5000033
Fix missing `lazy`-related symbols for the JIT (#34)
picnixz Dec 7, 2025
a0a28c2
Merge branch 'main' into lazy
Yhg1s Dec 7, 2025
023f806
Fix and improve `Objects/lazyimportobject.c` (#31)
picnixz Dec 8, 2025
4b352dc
Fix doc merge snafu.
Yhg1s Dec 8, 2025
7a0ddfd
Fix examples in the `ast` module docs (run by doctest).
Yhg1s Dec 8, 2025
6fd8c59
Don't include the new C API functions
Yhg1s Dec 8, 2025
a05b50d
Add the new builtin types to the known-to-be-mutable-global lists in
Yhg1s Dec 8, 2025
ac80f2d
Fix some crashes
pablogsal Dec 8, 2025
2d4bdcc
Document ImportCycleError and PyExc_ImportCycleError.
Yhg1s Dec 8, 2025
cd1878c
Use `self.assertIn/assertNotIn` instead of `self.assertTrue/assertFal…
StanFromIreland Dec 8, 2025
31b7fe9
Fix more cases where lazy is not highlighted in repl (#36)
StanFromIreland Dec 8, 2025
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
2 changes: 2 additions & 0 deletions Doc/c-api/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,8 @@ Exception types
* :exc:`FloatingPointError`
* * .. c:var:: PyObject *PyExc_GeneratorExit
* :exc:`GeneratorExit`
* * .. c:var:: PyObject *PyExc_ImportCycleError
* :exc:`ImportCycleError`
* * .. c:var:: PyObject *PyExc_ImportError
* :exc:`ImportError`
* * .. c:var:: PyObject *PyExc_IndentationError
Expand Down
52 changes: 52 additions & 0 deletions Doc/c-api/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,58 @@ Importing Modules

.. versionadded:: 3.14

.. c:function:: PyImport_LazyImportsMode PyImport_GetLazyImportsMode()

Gets the current lazy imports mode.

.. versionadded:: next

.. c:function:: PyObject* PyImport_GetLazyImportsFilter()

Return a :term:`strong reference` to the current lazy imports filter,
or ``NULL`` if none exists. This function always succeeds.

.. versionadded:: next

.. c:function:: int PyImport_SetLazyImportsMode(PyImport_LazyImportsMode mode)

Similar to :c:func:`PyImport_ImportModuleAttr`, but names are UTF-8 encoded
strings instead of Python :class:`str` objects.

This function always returns ``0``.

.. versionadded:: next

.. c:function:: int PyImport_SetLazyImportsFilter(PyObject *filter)

Sets the current lazy imports filter. The *filter* should be a callable that
will receive ``(importing_module_name, imported_module_name, [fromlist])``
when an import can potentially be lazy and that must return ``True`` if
the import should be lazy and ``False`` otherwise.

Return ``0`` on success and ``-1`` with an exception set otherwise.

.. versionadded:: next

.. c:type:: PyImport_LazyImportsMode

Enumeration of possible lazy import modes.

.. c:enumerator:: PyImport_LAZY_NORMAL

Respect the ``lazy`` keyword in source code. This is the default mode.

.. c:enumerator:: PyImport_LAZY_ALL

Make all imports lazy by default.

.. c:enumerator:: PyImport_LAZY_NONE

Disable lazy imports entirely. Even explicit ``lazy`` statements become
eager imports.

.. versionadded:: next

.. c:function:: PyObject* PyImport_CreateModuleFromInitfunc(PyObject *spec, PyObject* (*initfunc)(void))

This function is a building block that enables embedders to implement
Expand Down
9 changes: 6 additions & 3 deletions Doc/library/ast.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,8 @@ Imports
names=[
alias(name='x'),
alias(name='y'),
alias(name='z')])])
alias(name='z')],
is_lazy=0)])


.. class:: ImportFrom(module, names, level)
Expand All @@ -1134,7 +1135,8 @@ Imports
alias(name='x'),
alias(name='y'),
alias(name='z')],
level=0)])
level=0,
is_lazy=0)])


.. class:: alias(name, asname)
Expand All @@ -1152,7 +1154,8 @@ Imports
names=[
alias(name='a', asname='b'),
alias(name='c')],
level=2)])
level=2,
is_lazy=0)])

Control flow
^^^^^^^^^^^^
Expand Down
6 changes: 6 additions & 0 deletions Doc/library/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,12 @@ The following exceptions are the exceptions that are usually raised.

.. versionadded:: 3.6

.. exception:: ImportCycleError

A subclass of :exc:`ImportError` which is raised when a lazy import fails
because it (directly or indirectly) tries to import itself.

.. versionadded:: 3.15

.. exception:: IndexError

Expand Down
88 changes: 88 additions & 0 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,43 @@ always available. Unless explicitly noted otherwise, all variables are read-only

.. versionadded:: 3.11


.. function:: get_lazy_imports()

Returns the current lazy imports mode as a string.

* ``"normal"``: Only imports explicitly marked with the ``lazy`` keyword are lazy
* ``"all"``: All top-level imports are potentially lazy
* ``"none"``: All lazy imports are suppressed (even explicitly marked ones)

See also :func:`set_lazy_imports` and :pep:`810`.

.. versionadded:: 3.15


.. function:: get_lazy_imports_filter()

Returns the current lazy imports filter function, or ``None`` if no filter
is set.

The filter function is called for every potentially lazy import to determine
whether it should actually be lazy. See :func:`set_lazy_imports_filter` for
details on the filter function signature.

.. versionadded:: 3.15


.. function:: get_lazy_modules()

Returns a set of fully-qualified module names that have been lazily imported.
This is primarily useful for diagnostics and introspection.

Note that modules are removed from this set when they are reified (actually
loaded on first use).

.. versionadded:: 3.15


.. function:: getrefcount(object)

Return the reference count of the *object*. The count returned is generally one
Expand Down Expand Up @@ -1719,6 +1756,57 @@ always available. Unless explicitly noted otherwise, all variables are read-only

.. versionadded:: 3.11


.. function:: set_lazy_imports(mode)

Sets the global lazy imports mode. The *mode* parameter must be one of the
following strings:

* ``"normal"``: Only imports explicitly marked with the ``lazy`` keyword are lazy
* ``"all"``: All top-level imports become potentially lazy
* ``"none"``: All lazy imports are suppressed (even explicitly marked ones)

This function is intended for advanced users who need to control lazy imports
across their entire application. Library developers should generally not use
this function as it affects the runtime execution of applications.

In addition to the mode, lazy imports can be controlled via the filter
provided by :func:`set_lazy_imports_filter`.

See also :func:`get_lazy_imports` and :pep:`810`.

.. versionadded:: 3.15


.. function:: set_lazy_imports_filter(filter)

Sets the lazy imports filter callback. The *filter* parameter must be a
callable or ``None`` to clear the filter.

The filter function is called for every potentially lazy import to determine
whether it should actually be lazy. It must have the following signature::

def filter(importing_module: str, imported_module: str,
fromlist: tuple[str, ...] | None) -> bool

Where:

* *importing_module* is the name of the module doing the import
* *imported_module* is the name of the module being imported
* *fromlist* is the tuple of names being imported (for ``from ... import``
statements), or ``None`` for regular imports

The filter should return ``True`` to allow the import to be lazy, or
``False`` to force an eager import.

This is an advanced feature intended for specialized users who need
fine-grained control over lazy import behavior.

See also :func:`get_lazy_imports_filter` and :pep:`810`.

.. versionadded:: 3.15


.. function:: setprofile(profilefunc)

.. index::
Expand Down
12 changes: 12 additions & 0 deletions Doc/library/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,18 @@ Standard names are defined for the following types:
.. seealso:: :pep:`667`


.. data:: LazyImportType

The type of lazy import proxy objects. These objects are created when a
module is lazily imported and serve as placeholders until the module is
actually accessed. This type can be used to detect lazy imports
programmatically.

.. versionadded:: next

.. seealso:: :pep:`810`


.. data:: GetSetDescriptorType

The type of objects defined in extension modules with ``PyGetSetDef``, such
Expand Down
4 changes: 4 additions & 0 deletions Doc/reference/lexical_analysis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ Some names are only reserved under specific contexts. These are known as

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

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

.. versionchanged:: next
``lazy`` is now a soft keyword.

.. index::
single: _, identifiers
single: __, identifiers
Expand Down
57 changes: 54 additions & 3 deletions Doc/reference/simple_stmts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -748,14 +748,15 @@ The :keyword:`!import` statement
pair: name; binding
pair: keyword; from
pair: keyword; as
pair: keyword; lazy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest you move this to the below section on lazy specifically.

pair: exception; ImportError
single: , (comma); import statement

.. productionlist:: python-grammar
import_stmt: "import" `module` ["as" `identifier`] ("," `module` ["as" `identifier`])*
: | "from" `relative_module` "import" `identifier` ["as" `identifier`]
import_stmt: ["lazy"] "import" `module` ["as" `identifier`] ("," `module` ["as" `identifier`])*
: | ["lazy"] "from" `relative_module` "import" `identifier` ["as" `identifier`]
: ("," `identifier` ["as" `identifier`])*
: | "from" `relative_module` "import" "(" `identifier` ["as" `identifier`]
: | ["lazy"] "from" `relative_module` "import" "(" `identifier` ["as" `identifier`]
: ("," `identifier` ["as" `identifier`])* [","] ")"
: | "from" `relative_module` "import" "*"
module: (`identifier` ".")* `identifier`
Expand Down Expand Up @@ -870,6 +871,56 @@ determine dynamically the modules to be loaded.

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


.. _lazy-imports:
.. _lazy:

Lazy imports
------------

.. index::
pair: lazy; import
single: lazy import

The :keyword:`lazy` keyword marks an import as lazy. It is a :ref:`soft keyword
<soft-keywords>` that only has special meaning when it appears immediately
before an :keyword:`import` or :keyword:`from` statement.

When an import statement is preceded by the :keyword:`lazy` keyword,
the import becomes *lazy*: the module is not loaded immediately at the import
statement. Instead, a lazy proxy object is created and bound to the name. The
actual module is loaded on first use of that name.

Lazy imports are only permitted at module scope. Using ``lazy`` inside a
function, class body, or :keyword:`try`/:keyword:`except`/:keyword:`finally`
block raises a :exc:`SyntaxError`. Star imports cannot be lazy (``lazy from
module import *`` is a syntax error), and :ref:`future statements <future>`
cannot be lazy.

When using ``lazy from ... import``, each imported name is bound to a lazy
proxy object. The first access to any of these names triggers loading of the
entire module and resolves only that specific name to its actual value. Other
names remain as lazy proxies until they are accessed.

Example::

lazy import json
Comment on lines +906 to +907
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lazy import json
lazy import json
lazy import sys

For 'json' in sys.modules to run.


print('json' in sys.modules) # False - module not loaded yet

# First use triggers loading
result = json.dumps({"hello": "world"})

print('json' in sys.modules) # True - now loaded

If an error occurs during module loading (such as :exc:`ImportError` or
:exc:`SyntaxError`), it is raised at the point where the lazy import is first
used, not at the import statement itself.

See :pep:`810` for the full specification of lazy imports.

.. versionadded:: next

.. _future:

Future statements
Expand Down
19 changes: 19 additions & 0 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,14 @@ Miscellaneous options

.. versionadded:: 3.14

* :samp:`-X lazy_imports={all,none,normal}` controls lazy import behavior.
``all`` makes all imports lazy by default, ``none`` disables lazy imports
entirely (even explicit ``lazy`` statements become eager), and ``normal``
(the default) respects the ``lazy`` keyword in source code.
See also :envvar:`PYTHON_LAZY_IMPORTS`.

.. versionadded:: next

It also allows passing arbitrary values and retrieving them through the
:data:`sys._xoptions` dictionary.

Expand Down Expand Up @@ -1339,6 +1347,17 @@ conflict.

.. versionadded:: 3.14

.. envvar:: PYTHON_LAZY_IMPORTS

Controls lazy import behavior. Accepts three values: ``all`` makes all
imports lazy by default, ``none`` disables lazy imports entirely (even
explicit ``lazy`` statements become eager), and ``normal`` (the default)
respects the ``lazy`` keyword in source code.

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

.. versionadded:: next

Debug-mode variables
~~~~~~~~~~~~~~~~~~~~

Expand Down
Loading
Loading