@@ -246,7 +246,8 @@ The soft keyword is only allowed at the global (module) level, **not** inside
246246functions, class bodies, with ``try ``/``with `` blocks, or ``import * ``. Import
247247statements that use the soft keyword are *potentially lazy *. Imports that
248248can't be lazy are unaffected by the global lazy imports flag, and instead are
249- always eager.
249+ always eager. Additionally, ``from __future__ import `` statements cannot be
250+ lazy.
250251
251252Examples of syntax errors:
252253
@@ -273,6 +274,9 @@ Examples of syntax errors:
273274 # SyntaxError: lazy from ... import * is not allowed
274275 lazy from json import *
275276
277+ # SyntaxError: lazy from __future__ import is not allowed
278+ lazy from __future__ import annotations
279+
276280 Semantics
277281---------
278282
@@ -305,7 +309,7 @@ sequence of fully qualified module names (strings) to make *potentially lazy*
305309that module are also lazy, but not necessarily imports of sub-modules.
306310
307311The normal (non-lazy) import statement will check the global lazy imports
308- flag. If it is "enabled ", all imports are *potentially lazy * (except for
312+ flag. If it is "all ", all imports are *potentially lazy * (except for
309313imports that can't be lazy, as mentioned above.)
310314
311315Example:
@@ -318,7 +322,7 @@ Example:
318322 result = json.dumps({" hello" : " world" })
319323 print (' json' in sys.modules) # True
320324
321- If the global lazy imports flag is set to "disabled ", no *potentially lazy *
325+ If the global lazy imports flag is set to "none ", no *potentially lazy *
322326import is ever imported lazily, and the behavior is equivalent to a regular
323327import statement: the import is *eager * (as if the lazy keyword was not used).
324328
@@ -566,20 +570,27 @@ After several calls, ``LOAD_GLOBAL`` specializes to ``LOAD_GLOBAL_MODULE``:
566570 Lazy imports filter
567571-------------------
568572
569- This PEP adds two new functions to the ``sys `` module to manage the lazy
570- imports filter:
573+ This PEP adds the following new functions to manage the lazy imports filter:
574+
575+ * ``importlib.set_lazy_imports_filter(func) `` - Sets the filter function. If
576+ ``func=None `` then the import filter is removed. The ``func `` parameter must
577+ have the signature: ``func(importer: str, name: str, fromlist: tuple[str, ...] | None) -> bool ``
571578
572- * ``sys.set_lazy_imports_filter(func) `` - Sets the filter function. The
573- ``func `` parameter must have the signature: ``func(importer: str, name: str,
574- fromlist: tuple[str, ...] | None) -> bool ``
579+ * ``importlib.get_lazy_imports_filter() `` - Returns the currently installed
580+ filter function, or ``None `` if no filter is set.
575581
576- * ``sys.get_lazy_imports_filter() `` - Returns the currently installed filter
577- function, or ``None `` if no filter is set.
582+ * ``importlib.set_lazy_imports(enabled=None, /) `` - Programmatic API for
583+ controlling lazy imports at runtime. The ``enabled `` parameter can be
584+ ``None `` (respect ``lazy `` keyword only), ``True `` (force all imports to be
585+ potentially lazy), or ``False `` (force all imports to be eager).
578586
579587The filter function is called for every potentially lazy import, and must
580588return ``True `` if the import should be lazy. This allows for fine-grained
581589control over which imports should be lazy, useful for excluding modules with
582- known side-effect dependencies or registration patterns.
590+ known side-effect dependencies or registration patterns. The filter function
591+ is called at the point of execution of the lazy import or lazy from import
592+ statement, not at the point of reification. The filter function may be
593+ called concurrently.
583594
584595The filter mechanism serves as a foundation that tools, debuggers, linters,
585596and other ecosystem utilities can leverage to provide better lazy import
@@ -619,7 +630,7 @@ Example:
619630 return True # Allow lazy import
620631
621632 # Install the filter
622- sys .set_lazy_imports_filter(exclude_side_effect_modules)
633+ importlib .set_lazy_imports_filter(exclude_side_effect_modules)
623634
624635 # These imports are checked by the filter
625636 lazy import data_processor # Filter returns True -> stays lazy
@@ -639,31 +650,33 @@ The global lazy imports flag can be controlled through:
639650
640651* The ``-X lazy_imports=<mode> `` command-line option
641652* The ``PYTHON_LAZY_IMPORTS=<mode> `` environment variable
642- * The ``sys .set_lazy_imports(mode) `` function (primarily for testing)
653+ * The ``importlib .set_lazy_imports(mode) `` function (primarily for testing)
643654
644655Where ``<mode> `` can be:
645656
646- * ``"default " `` (or unset): Only explicitly marked lazy imports are lazy
657+ * ``"normal " `` (or unset): Only explicitly marked lazy imports are lazy
647658
648- * ``"enabled " ``: All module-level imports (except in ``try `` or ``with ``
659+ * ``"all " ``: All module-level imports (except in ``try `` or ``with ``
649660 blocks and ``import * ``) become *potentially lazy *
650661
651- * ``"disabled " ``: No imports are lazy, even those explicitly marked with
662+ * ``"none " ``: No imports are lazy, even those explicitly marked with
652663 ``lazy `` keyword
653664
654- When the global flag is set to ``"enabled " ``, all imports at the global level
665+ When the global flag is set to ``"all " ``, all imports at the global level
655666of all modules are *potentially lazy * **except ** for those inside a ``try `` or
656667``with `` block or any wild card (``from ... import * ``) import.
657668
658- If the global lazy imports flag is set to ``"disabled " ``, no *potentially
669+ If the global lazy imports flag is set to ``"none " ``, no *potentially
659670lazy * import is ever imported lazily, the import filter is never called, and
660671the behavior is equivalent to a regular ``import `` statement: the import is
661672*eager * (as if the lazy keyword was not used).
662673
663- Python code can run the :func: `!sys .set_lazy_imports ` function to override
674+ Python code can run the :func: `!importlib .set_lazy_imports ` function to override
664675the state of the global lazy imports flag inherited from the environment or CLI.
665676This is especially useful if an application needs to ensure that all imports
666- are evaluated eagerly, via ``sys.set_lazy_imports('disabled') ``.
677+ are evaluated eagerly, via ``importlib.set_lazy_imports('none') ``.
678+ Alternatively, :func: `!importlib.set_lazy_imports ` can be used with boolean
679+ values for programmatic control.
667680
668681
669682Backwards Compatibility
@@ -760,7 +773,7 @@ The `pyperformance suite`_ confirms the implementation is performance-neutral.
760773Filter function performance
761774~~~~~~~~~~~~~~~~~~~~~~~~~~~~
762775
763- The filter function (set via ``sys .set_lazy_imports_filter() ``) is called for
776+ The filter function (set via ``importlib .set_lazy_imports_filter() ``) is called for
764777every *potentially lazy * import to determine whether it should actually be
765778lazy. When no filter is set, this is simply a NULL check (testing whether a
766779filter function has been registered), which is a highly predictable branch that
@@ -857,9 +870,18 @@ see:
857870- `Inside HRT's Python Fork
858871 <https://www.hudsonrivertrading.com/hrtbeat/inside-hrts-python-fork/> `__
859872 (Hudson River Trading)
873+ - `Create an On-Demand Initializer for PySide
874+ <https://bugreports.qt.io/browse/PYSIDE-2404> `__
875+ (Qt for Python/PySide) - Christian Tismer's implementation of lazy
876+ initialization for PySide6 based on ideas from :pep: `690 `, showing 10-20%
877+ startup time improvement for PySide applications. This demonstrates the
878+ particular value of lazy imports for frameworks with extensive
879+ initialization at import time.
860880
861881The benefits scale with codebase complexity: the larger and more
862- interconnected the codebase, the more dramatic the improvements.
882+ interconnected the codebase, the more dramatic the improvements. The
883+ PySide implementation particularly highlights how frameworks with heavy
884+ initialization overhead can benefit significantly from opt-in lazy loading.
863885
864886Typing and tools
865887----------------
@@ -874,6 +896,10 @@ Security Implications
874896=====================
875897
876898There are no known security vulnerabilities introduced by lazy imports.
899+ Security-sensitive tools that need to ensure all imports are evaluated eagerly
900+ can use :func: `!importlib.set_lazy_imports ` with ``enabled=False `` to force
901+ eager evaluation, or use :func: `!importlib.set_lazy_imports_filter ` for fine-grained
902+ control.
877903
878904How to Teach This
879905=================
@@ -889,6 +915,14 @@ profiling to understand the import time overhead in their codebase and mark
889915the necessary imports as ``lazy ``. In addition, developers can mark imports
890916that will only be used for type annotations as ``lazy ``.
891917
918+ Additional documentation will be added to the Python documentation, including
919+ guidance, a dedicated how-to guide, and updates to the import system
920+ documentation covering: identifying slow-loading modules with profiling tools
921+ (such as ``-X importtime ``), migration strategies for existing codebases, best
922+ practices for avoiding common pitfalls with import-time side effects, and
923+ patterns for using lazy imports effectively with type annotations and circular
924+ imports.
925+
892926Below is guidance on how to best take advantage of lazy imports and how to
893927avoid incompatibilities:
894928
@@ -1215,14 +1249,14 @@ exclude specific modules that are known to have problematic side effects:
12151249 return False # Import eagerly
12161250 return True # Allow lazy import
12171251
1218- sys .set_lazy_imports_filter(my_filter)
1252+ importlib .set_lazy_imports_filter(my_filter)
12191253
12201254 The filter function receives the importer module name, the module being
12211255imported, and the fromlist (if using ``from ... import ``). Returning ``False ``
12221256forces an eager import.
12231257
1224- Alternatively, set the global mode to ``"disabled " `` via ``-X
1225- lazy_imports=disabled `` to turn off all lazy imports for debugging.
1258+ Alternatively, set the global mode to ``"none " `` via ``-X
1259+ lazy_imports=none `` to turn off all lazy imports for debugging.
12261260
12271261Can I use lazy imports inside functions?
12281262----------------------------------------
@@ -1416,11 +1450,105 @@ No, future imports can't be lazy because they're parser/compiler directives.
14161450It's technically possible for the runtime behavior to be lazy but there's no
14171451real value in it.
14181452
1419- Why you chose ``lazy `` as the keyword name?
1420- -------------------------------------------
1453+ Why did you choose ``lazy `` as the keyword name?
1454+ ------------------------------------------------
14211455
14221456Not "why"... memorize! :)
14231457
1458+ Deferred Ideas
1459+ ==============
1460+
1461+ The following ideas have been considered but are deliberately deferred to focus
1462+ on delivering a stable, usable core feature first. These may be considered for
1463+ future enhancements once we have real-world experience with lazy imports.
1464+
1465+ Alternative syntax and ergonomic improvements
1466+ ----------------------------------------------
1467+
1468+ Several alternative syntax forms have been suggested to improve ergonomics:
1469+
1470+ * **Type-only imports **: A specialized syntax for imports used exclusively in
1471+ type annotations (similar to the ``type `` keyword in other contexts) could be
1472+ added, such as ``type from collections.abc import Sequence ``. This would make
1473+ the intent clearer than using ``lazy `` for type-only imports and would signal
1474+ to readers that the import is never used at runtime. However, since ``lazy ``
1475+ imports already solve the runtime cost problem for type annotations, we prefer
1476+ to start with the simpler, more general mechanism and evaluate whether
1477+ specialized syntax adds sufficient value after gathering usage data.
1478+
1479+ * **Block-based syntax **: Grouping multiple lazy imports in a block, such as:
1480+
1481+ .. code-block :: python
1482+
1483+ as lazy:
1484+ import foo
1485+ from bar import baz
1486+
1487+ This could reduce repetition when marking many imports as lazy. However, it
1488+ would require introducing an entirely new statement form (``as lazy: `` blocks)
1489+ that doesn't fit into Python's existing grammar patterns. It's unclear how
1490+ this would interact with other language features or what the precedent would
1491+ be for similar block-level modifiers. This approach also makes it less clear
1492+ when scanning code whether a particular import is lazy, since you must look at
1493+ the surrounding context rather than the import line itself.
1494+
1495+ While these alternatives could provide different ergonomics in certain contexts,
1496+ they share similar drawbacks: they would require introducing new statement
1497+ forms or overloading existing syntax in non-obvious ways, and they open the
1498+ door to many other potential uses of similar syntax patterns that would
1499+ significantly expand the language. We prefer to start with the explicit
1500+ ``lazy import `` syntax and gather real-world feedback before considering
1501+ additional syntax variations. Any future ergonomic improvements should be
1502+ evaluated based on actual usage patterns rather than speculative benefits.
1503+
1504+ Automatic lazy imports for ``if TYPE_CHECKING `` blocks
1505+ -------------------------------------------------------
1506+
1507+ A future enhancement could automatically treat all imports inside
1508+ ``if TYPE_CHECKING: `` blocks as lazy:
1509+
1510+ .. code-block :: python
1511+
1512+ from typing import TYPE_CHECKING
1513+
1514+ if TYPE_CHECKING :
1515+ from foo import Bar # Could be automatically lazy
1516+
1517+ However, this would require significant changes to make this work at compile
1518+ time, since ``TYPE_CHECKING `` is currently just a runtime variable. The
1519+ compiler would need special knowledge of this pattern, similar to how
1520+ ``from __future__ import `` statements are handled. Additionally, making
1521+ ``TYPE_CHECKING `` a built-in would be required for this to work reliably.
1522+ Since ``lazy `` imports already solve the runtime cost problem for type-only
1523+ imports, we prefer to start with the explicit syntax and evaluate whether
1524+ this optimization adds sufficient value.
1525+
1526+ Module-level lazy import mode
1527+ ------------------------------
1528+
1529+ A module-level declaration to make all imports in that module lazy by default:
1530+
1531+ .. code-block :: python
1532+
1533+ from __future__ import lazy_imports
1534+ import foo # Automatically lazy
1535+
1536+ This was discussed but deferred because it raises several questions. Using
1537+ ``from __future__ import `` implies this would become the default behavior in a
1538+ future Python version, which is unclear and not currently planned. It also
1539+ raises questions about how such a mode would interact with the global flag and
1540+ what the transition path would look like. The current explicit syntax and
1541+ ``__lazy_modules__ `` provide sufficient control for initial adoption.
1542+
1543+ Package metadata for lazy-safe declarations
1544+ --------------------------------------------
1545+
1546+ Future enhancements could allow packages to declare in their metadata whether
1547+ they are safe for lazy importing (e.g., no import-time side effects). This
1548+ could be used by the filter mechanism or by static analysis tools. The current
1549+ filter API is designed to accommodate such future additions without requiring
1550+ changes to the core language specification.
1551+
14241552Alternate Implementation Ideas
14251553==============================
14261554
@@ -1560,6 +1688,30 @@ to add more specific re-enabling mechanisms later, when we have a clearer
15601688picture of real-world use and patterns, than it is to remove a hastily added
15611689mechanism that isn't quite right.
15621690
1691+ Using a decorator syntax for lazy imports
1692+ ------------------------------------------
1693+
1694+ A decorator-based syntax could mark imports as lazy:
1695+
1696+ .. code-block :: python
1697+
1698+ @lazy
1699+ import json
1700+
1701+ @lazy
1702+ from foo import bar
1703+
1704+ This approach was rejected because it introduces too many open questions and
1705+ complications. Decorators in Python are designed to wrap and transform callable
1706+ objects (functions, classes, methods), not statements. Allowing decorators on
1707+ import statements would open the door to many other potential statement
1708+ decorators (``@cached ``, ``@traced ``, ``@deprecated ``, etc.), significantly
1709+ expanding the language's syntax in ways we don't want to explore. Furthermore,
1710+ this raises the question of where such decorators would come from: they would
1711+ need to be either imported or built-in, creating a bootstrapping problem for
1712+ import-related decorators. This is far more speculative and generic than the
1713+ focused ``lazy import `` syntax.
1714+
15631715Using a context manager instead of a new soft keyword
15641716-----------------------------------------------------
15651717
0 commit comments