@@ -246,7 +246,7 @@ Syntax restrictions
246246~~~~~~~~~~~~~~~~~~~
247247
248248The soft keyword is only allowed at the global (module) level, **not ** inside
249- functions, class bodies, with ``try ``/`` with `` blocks, or ``import * ``. Import
249+ functions, class bodies, ``try `` blocks, or ``import * ``. Import
250250statements that use the soft keyword are *potentially lazy *. Imports that
251251can't be lazy are unaffected by the global lazy imports flag, and instead are
252252always eager. Additionally, ``from __future__ import `` statements cannot be
@@ -270,10 +270,6 @@ Examples of syntax errors:
270270 except ImportError :
271271 pass
272272
273- # SyntaxError: lazy import not allowed inside with blocks
274- with suppress(ImportError ):
275- lazy import json
276-
277273 # SyntaxError: lazy from ... import * is not allowed
278274 lazy from json import *
279275
@@ -465,54 +461,37 @@ immediately resolve all lazy objects (e.g. ``lazy from`` statements) that
465461referenced the module. It **only ** resolves the lazy object being accessed.
466462
467463Accessing a lazy object (from a global variable or a module attribute) reifies
468- the object. Accessing a module's ``__dict__ `` reifies **all ** lazy objects in
469- that module. Calling ``dir() `` at the global scope will not reify the globals
470- and calling ``dir(mod) `` will be special cased in ``mod.__dir__ `` avoid
471- reification as well.
472-
473- Example using ``__dict__ `` from external code:
474-
475- .. code-block :: python
476-
477- # my_module.py
478- import sys
479- lazy import json
464+ the object.
480465
481- print (' json' in sys.modules) # False - still lazy
482-
483- # main.py
484- import sys
485- import my_module
486-
487- # Accessing __dict__ from external code DOES reify all lazy imports
488- d = my_module.__dict__
489-
490- print (' json' in sys.modules) # True - reified by __dict__ access
491- print (type (d[' json' ])) # <class 'module'>
466+ However, calling ``globals() `` or accessing a module's ``__dict__ `` does
467+ **not ** trigger reification -- they return the module's dictionary, and
468+ accessing lazy objects through that dictionary still returns lazy proxy
469+ objects that need to be manually reified upon use. A lazy object can be
470+ resolved explicitly by calling the ``resolve `` method. Calling ``dir() `` at
471+ the global scope will not reify the globals, nor will calling ``dir(mod) ``
472+ (through special-casing in ``mod.__dir__ ``.) Other, more indirect ways of
473+ accessing arbitrary globals (e.g. inspecting ``frame.f_globals ``) also do
474+ **not ** reify all the objects.
492475
493- However, calling ``globals() `` does **not ** trigger reification -- it returns
494- the module's dictionary, and accessing lazy objects through that dictionary
495- still returns lazy proxy objects that need to be manually reified upon use. A
496- lazy object can be resolved explicitly by calling the ``resolve `` method.
497- Other, more indirect ways of accessing arbitrary globals (e.g. inspecting
498- ``frame.f_globals ``) also do **not ** reify all the objects.
499-
500- Example using ``globals() ``:
476+ Example using ``globals() `` and ``__dict__ ``:
501477
502478.. code-block :: python
503479
480+ # my_module.py
504481 import sys
505482 lazy import json
506483
507484 # Calling globals() does NOT trigger reification
508485 g = globals ()
509-
510486 print (' json' in sys.modules) # False - still lazy
511487 print (type (g[' json' ])) # <class 'LazyImport'>
512488
489+ # Accessing __dict__ also does NOT trigger reification
490+ d = __dict__
491+ print (type (d[' json' ])) # <class 'LazyImport'>
492+
513493 # Explicitly reify using the resolve() method
514494 resolved = g[' json' ].resolve()
515-
516495 print (type (resolved)) # <class 'module'>
517496 print (' json' in sys.modules) # True - now loaded
518497
@@ -707,15 +686,15 @@ Where ``<mode>`` can be:
707686
708687* ``"normal" `` (or unset): Only explicitly marked lazy imports are lazy
709688
710- * ``"all" ``: All module-level imports (except in ``try `` or `` with ``
689+ * ``"all" ``: All module-level imports (except in ``try ``
711690 blocks and ``import * ``) become *potentially lazy *
712691
713692* ``"none" ``: No imports are lazy, even those explicitly marked with
714693 ``lazy `` keyword
715694
716695When the global flag is set to ``"all" ``, all imports at the global level
717- of all modules are *potentially lazy * **except ** for those inside a ``try `` or
718- `` with `` block or any wild card (``from ... import * ``) import.
696+ of all modules are *potentially lazy * **except ** for those inside a ``try ``
697+ block or any wild card (``from ... import * ``) import.
719698
720699If the global lazy imports flag is set to ``"none" ``, no *potentially
721700lazy * import is ever imported lazily, the import filter is never called, and
@@ -1251,36 +1230,9 @@ either the lazy proxy or the final resolved object.
12511230Can I force reification of a lazy import without using it?
12521231----------------------------------------------------------
12531232
1254- Yes, accessing a module's ``__dict__ `` will reify all lazy objects in that
1255- module. Individual lazy objects can be resolved by calling their ``resolve() ``
1233+ Yes, individual lazy objects can be resolved by calling their ``resolve() ``
12561234method.
12571235
1258- What's the difference between ``globals() `` and ``mod.__dict__ `` for lazy imports?
1259- ----------------------------------------------------------------------------------
1260-
1261- Calling ``globals() `` returns the module's dictionary without reifying lazy
1262- imports -- you'll see lazy proxy objects when accessing them through the
1263- returned dictionary. However, accessing ``mod.__dict__ `` from external code
1264- reifies all lazy imports in that module first. This design ensures:
1265-
1266- .. code-block :: python
1267-
1268- # In your module:
1269- lazy import json
1270-
1271- g = globals ()
1272- print (type (g[' json' ])) # <class 'LazyImport'> - your problem
1273-
1274- # From external code:
1275- import sys
1276- mod = sys.modules[' your_module' ]
1277- d = mod.__dict__
1278- print (type (d[' json' ])) # <class 'module'> - reified for external access
1279-
1280- This distinction means adding lazy imports and calling ``globals() `` is your
1281- responsibility to manage, while external code accessing ``mod.__dict__ ``
1282- always sees fully loaded modules.
1283-
12841236Why not use ``importlib.util.LazyLoader `` instead?
12851237--------------------------------------------------
12861238
@@ -1664,6 +1616,52 @@ From the discussion on :pep:`690` it is clear that this is a fairly
16641616contentious idea, although perhaps once we have wide-spread use of lazy
16651617imports this can be reconsidered.
16661618
1619+ Disallowing lazy imports inside ``with `` blocks
1620+ ------------------------------------------------
1621+
1622+ An earlier version of this PEP proposed disallowing ``lazy import `` statements
1623+ inside ``with `` blocks, similar to the restriction on ``try `` blocks. The
1624+ concern was that certain context managers (like ``contextlib.suppress(ImportError) ``)
1625+ could suppress import errors in confusing ways when combined with lazy imports.
1626+
1627+ However, this restriction was rejected because ``with `` statements have much
1628+ broader semantics than ``try/except `` blocks. While ``try/except `` is explicitly
1629+ about catching exceptions, ``with `` blocks are commonly used for resource
1630+ management, temporary state changes, or scoping -- contexts where lazy imports
1631+ work perfectly fine. The ``lazy import `` syntax is explicit enough that
1632+ developers who write it inside a ``with `` block are making an intentional choice,
1633+ aligning with Python's "consenting adults" philosophy. For genuinely problematic
1634+ cases like ``with suppress(ImportError): lazy import foo ``, static analysis
1635+ tools and linters are better suited to catch these patterns than hard language
1636+ restrictions.
1637+
1638+ Forcing eager imports in ``with `` blocks under the global flag
1639+ ---------------------------------------------------------------
1640+
1641+ Another rejected idea was to make imports inside ``with `` blocks remain eager
1642+ even when the global lazy imports flag is set to ``"all" ``. The rationale was
1643+ to be conservative: since ``with `` statements can affect how imports behave
1644+ (e.g., by modifying ``sys.path `` or suppressing exceptions), forcing imports to
1645+ remain eager could prevent subtle bugs. However, this would create inconsistent
1646+ behavior where ``lazy import `` is allowed explicitly in ``with `` blocks, but
1647+ normal imports remain eager when the global flag is enabled. This inconsistency
1648+ between explicit and implicit laziness is confusing and hard to explain.
1649+
1650+ The simpler, more consistent rule is that the global flag affects imports
1651+ everywhere that explicit ``lazy import `` syntax is allowed. This avoids having
1652+ three different sets of rules (explicit syntax, global flag behavior, and filter
1653+ mechanism) and instead provides two: explicit syntax rules match what the global
1654+ flag affects, and the filter mechanism provides escape hatches for edge cases.
1655+ For users who need fine-grained control, the filter mechanism
1656+ (``sys.set_lazy_imports_filter() ``) already provides a way to exclude specific
1657+ imports or patterns. Additionally, there's no inverse operation: if the global
1658+ flag forces imports eager in ``with `` blocks but a user wants them lazy, there's
1659+ no way to override it, creating an asymmetry.
1660+
1661+ In summary: imports in ``with `` blocks behave consistently whether marked
1662+ explicitly with ``lazy import `` or implicitly via the global flag, creating a
1663+ simple rule that's easy to explain and reason about.
1664+
16671665Modification of the dict object
16681666-------------------------------
16691667
@@ -1868,55 +1866,38 @@ from a real dict in almost all cases, which is extremely difficult to achieve
18681866correctly. Any deviation from true dict behavior would be a source of subtle
18691867bugs.
18701868
1871- Reifying lazy imports when ``globals() `` is called
1872- ---------------------------------------------------
1869+ Automatically reifying on `` __dict__ `` or ``globals() `` access
1870+ --------------------------------------------------------------
18731871
1874- Calling ``globals() `` returns the module's namespace dictionary without
1875- triggering reification of lazy imports. Accessing lazy objects through the
1876- returned dictionary yields the lazy proxy objects themselves. This is an
1877- intentional design decision for several reasons:
1878-
1879- **The key distinction **: Adding a lazy import and calling ``globals() `` is the
1880- module author's concern and under their control. However, accessing
1881- ``mod.__dict__ `` from external code is a different scenario -- it crosses
1882- module boundaries and affects someone else's code. Therefore, ``mod.__dict__ ``
1883- access reifies all lazy imports to ensure external code sees fully realized
1884- modules, while ``globals() `` preserves lazy objects for the module's own
1885- introspection needs.
1886-
1887- **Technical challenges **: It is impossible to safely reify on-demand when
1888- ``globals() `` is called because we cannot return a proxy dictionary -- this
1889- would break common usages like passing the result to ``exec() `` or other
1890- built-ins that expect a real dictionary. The only alternative would be to
1891- eagerly reify all lazy imports whenever ``globals() `` is called, but this
1892- behavior would be surprising and potentially expensive.
1893-
1894- **Performance concerns **: It is impractical to cache whether a reification
1895- scan has been performed with just the globals dictionary reference, whereas
1896- module attribute access (the primary use case) can efficiently cache
1897- reification state in the module object itself.
1898-
1899- **Use case rationale **: The chosen design makes sense precisely because of
1900- this distinction: adding a lazy import and calling ``globals() `` is your
1901- problem to manage, while having lazy imports visible in ``mod.__dict__ ``
1902- becomes someone else's problem. By reifying on ``__dict__ `` access but not on
1903- ``globals() ``, we ensure external code always sees fully loaded modules while
1904- giving module authors control over their own introspection.
1905-
1906- Note that three options were considered:
1872+ Three options were considered for how ``globals() `` and ``mod.__dict__ `` should
1873+ behave with lazy imports:
19071874
190818751. Calling ``globals() `` or ``mod.__dict__ `` traverses and resolves all lazy
19091876 objects before returning.
191018772. Calling ``globals() `` or ``mod.__dict__ `` returns the dictionary with lazy
1911- objects present.
1878+ objects present (chosen) .
191218793. Calling ``globals() `` returns the dictionary with lazy objects, but
19131880 ``mod.__dict__ `` reifies everything.
19141881
1915- We chose the third option because it properly delineates responsibility: if
1916- you add lazy imports to your module and call ``globals() ``, you're responsible
1917- for handling the lazy objects. But external code accessing your module's
1918- ``__dict__ `` shouldn't need to know about your lazy imports -- it gets fully
1919- resolved modules.
1882+ We chose option 2: both ``globals() `` and ``__dict__ `` return the raw
1883+ namespace dictionary without triggering reification. This provides a clean,
1884+ predictable model where low-level introspection APIs don't trigger side
1885+ effects.
1886+
1887+ Having ``globals() `` and ``__dict__ `` behave identically creates symmetry and
1888+ a simple mental model: both expose the raw namespace view. Low-level
1889+ introspection APIs should not automatically trigger imports, which would be
1890+ surprising and potentially expensive. Real-world experience implementing lazy
1891+ imports in the standard library (such as the traceback module) showed that
1892+ automatic reification on ``__dict__ `` access was cumbersome and forced
1893+ introspection code to load modules it was only examining.
1894+
1895+ Option 1 (always reifying) was rejected because it would make ``globals() ``
1896+ and ``__dict__ `` access surprisingly expensive and prevent introspecting the
1897+ lazy state of a module. Option 3 was initially considered to "protect" external
1898+ code from seeing lazy objects, but real-world usage showed this created more
1899+ problems than it solved, particularly for stdlib code that needs to introspect
1900+ modules without triggering side effects.
19201901
19211902Acknowledgements
19221903================
0 commit comments