Skip to content

Commit e6cb131

Browse files
committed
Implement more of PEP 810
1 parent b743eb0 commit e6cb131

File tree

16 files changed

+850
-356
lines changed

16 files changed

+850
-356
lines changed

Doc/library/sys.rst

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,43 @@ 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 are lazy
920+
* ``"all"``: All top-level imports are potentially lazy
921+
* ``"none"``: All lazy imports are suppressed (even explicitly marked ones)
922+
923+
See also :func:`set_lazy_imports` and :pep:`810`.
924+
925+
.. versionadded:: 3.15
926+
927+
928+
.. function:: get_lazy_imports_filter()
929+
930+
Returns the current lazy imports filter function, or ``None`` if no filter
931+
is set.
932+
933+
The filter function is called for every potentially lazy import to determine
934+
whether it should actually be lazy. See :func:`set_lazy_imports_filter` for
935+
details on the filter function signature.
936+
937+
.. versionadded:: 3.15
938+
939+
940+
.. function:: get_lazy_modules()
941+
942+
Returns a set of fully-qualified module names that have been lazily imported.
943+
This is primarily useful for diagnostics and introspection.
944+
945+
Note that modules are removed from this set when they are reified (actually
946+
loaded on first use).
947+
948+
.. versionadded:: 3.15
949+
950+
914951
.. function:: getrefcount(object)
915952

916953
Return the reference count of the *object*. The count returned is generally one
@@ -1715,6 +1752,57 @@ always available. Unless explicitly noted otherwise, all variables are read-only
17151752

17161753
.. versionadded:: 3.11
17171754

1755+
1756+
.. function:: set_lazy_imports(mode)
1757+
1758+
Sets the global lazy imports mode. The *mode* parameter must be one of the
1759+
following strings:
1760+
1761+
* ``"normal"``: Only imports explicitly marked with the ``lazy`` keyword are lazy
1762+
* ``"all"``: All top-level imports become potentially lazy
1763+
* ``"none"``: All lazy imports are suppressed (even explicitly marked ones)
1764+
1765+
This function is intended for advanced users who need to control lazy imports
1766+
across their entire application. Library developers should generally not use
1767+
this function as it affects the runtime execution of applications.
1768+
1769+
In addition to the mode, lazy imports can be controlled via the filter
1770+
provided by :func:`set_lazy_imports_filter`.
1771+
1772+
See also :func:`get_lazy_imports` and :pep:`810`.
1773+
1774+
.. versionadded:: 3.15
1775+
1776+
1777+
.. function:: set_lazy_imports_filter(filter)
1778+
1779+
Sets the lazy imports filter callback. The *filter* parameter must be a
1780+
callable or ``None`` to clear the filter.
1781+
1782+
The filter function is called for every potentially lazy import to determine
1783+
whether it should actually be lazy. It must have the following 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:: 3.15
1804+
1805+
17181806
.. function:: setprofile(profilefunc)
17191807

17201808
.. index::

Doc/whatsnew/3.15.rst

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ Summary -- Release highlights
6565
6666
.. PEP-sized items next.
6767
68+
* :pep:`810`: :ref:`Explicit lazy imports for faster startup times
69+
<whatsnew315-pep810>`
6870
* :pep:`799`: :ref:`A dedicated profiling package for organizing Python
6971
profiling tools <whatsnew315-sampling-profiler>`
7072
* :pep:`686`: :ref:`Python now uses UTF-8 as the default encoding
@@ -77,6 +79,97 @@ Summary -- Release highlights
7779
New features
7880
============
7981

82+
.. _whatsnew315-pep810:
83+
84+
:pep:`810`: Explicit lazy imports
85+
---------------------------------
86+
87+
Large Python applications often suffer from slow startup times. A significant
88+
contributor to this problem is the import system: when a module is imported,
89+
Python must locate the file, read it from disk, compile it to bytecode, and
90+
execute all top-level code. For applications with deep dependency trees, this
91+
process can take seconds, even when most of the imported code is never actually
92+
used during a particular run.
93+
94+
Developers have worked around this by moving imports inside functions, using
95+
``importlib`` to load modules on demand, or restructuring code to avoid
96+
unnecessary dependencies. These approaches work but make code harder to read
97+
and maintain, scatter import statements throughout the codebase, and require
98+
discipline to apply consistently.
99+
100+
Python now provides a cleaner solution through explicit lazy imports using the
101+
new ``lazy`` soft keyword. When you mark an import as lazy, Python defers the
102+
actual module loading until the imported name is first used. This gives you
103+
the organizational benefits of declaring all imports at the top of the file
104+
while only paying the loading cost for modules you actually use.
105+
106+
The ``lazy`` keyword works with both ``import`` and ``from ... import`` statements.
107+
When you write ``lazy import heavy_module``, Python does not immediately load the
108+
module. Instead, it creates a lightweight proxy object. The actual module loading
109+
happens transparently when you first access the name:
110+
111+
.. code-block:: python
112+
113+
lazy import json
114+
lazy from datetime import datetime
115+
116+
print("Starting up...") # json and datetime not loaded yet
117+
118+
data = json.loads('{"key": "value"}') # json loads here
119+
now = datetime() # datetime loads here
120+
121+
This mechanism is particularly useful for applications that import many modules
122+
at the top level but may only use a subset of them in any given run. The deferred
123+
loading reduces startup latency without requiring code restructuring or conditional
124+
imports scattered throughout the codebase.
125+
126+
When a lazy import eventually fails (for example, if the module does not exist),
127+
Python raises the exception at the point of first use rather than at import time.
128+
The traceback includes both the location where the name was accessed and the
129+
original import statement, making it straightforward to diagnose the problem.
130+
131+
For cases where you want to enable lazy loading globally without modifying source
132+
code, Python provides the :option:`-X lazy_imports <-X>` command-line option and
133+
the :envvar:`PYTHON_LAZY_IMPORTS` environment variable. Both accept three values:
134+
``all`` makes all imports lazy by default, ``none`` disables lazy imports entirely
135+
(even explicit ``lazy`` statements become eager), and ``normal`` (the default)
136+
respects the ``lazy`` keyword in source code. The :func:`sys.set_lazy_imports` and
137+
:func:`sys.get_lazy_imports` functions allow changing and querying this mode at
138+
runtime.
139+
140+
For more selective control, :func:`sys.set_lazy_imports_filter` accepts a callable
141+
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:
145+
146+
.. code-block:: python
147+
148+
import sys
149+
150+
sys.set_lazy_imports_filter(lambda name: name.startswith("myapp."))
151+
sys.set_lazy_imports("all")
152+
153+
import myapp.slow_module # lazy (matches filter)
154+
import json # eager (does not match filter)
155+
156+
For debugging and introspection, :func:`sys.get_lazy_modules` returns a set
157+
containing the names of all modules that have been lazily imported but not yet
158+
loaded. The proxy type itself is available as :data:`types.LazyImportType` for
159+
code that needs to detect lazy imports programmatically.
160+
161+
There are some restrictions on where ``lazy`` can appear. Lazy imports are only
162+
permitted at module scope; using ``lazy`` inside a function, class body, or
163+
``try``/``except``/``finally`` block raises a :exc:`SyntaxError`. Star imports
164+
cannot be lazy (``lazy from module import *`` is a syntax error), and future
165+
imports cannot be lazy either (``lazy from __future__ import ...`` raises
166+
:exc:`SyntaxError`).
167+
168+
.. seealso:: :pep:`810` for the full specification and rationale.
169+
170+
(Contributed by Pablo Galindo Salgado and Dino Viehland.)
171+
172+
80173
.. _whatsnew315-sampling-profiler:
81174

82175
:pep:`799`: High frequency statistical sampling profiler

Include/internal/pycore_interp_structs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ struct _import_state {
318318
PyObject *lazy_imports_filter;
319319
PyObject *lazy_importing_modules;
320320
PyObject *lazy_modules;
321+
PyObject *lazy_modules_set; /* Set of fully-qualified module names lazily imported (PEP 810) */
321322
/* The global import lock. */
322323
_PyRecursiveMutex lock;
323324
/* diagnostic info in PyImport_ImportModuleLevelObject() */

0 commit comments

Comments
 (0)