@@ -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
464+ the object. Calling ``dir() `` at the global scope will not reify the globals
465+ and calling ``dir(mod) `` will be special cased in ``mod.__dir__ `` to avoid
471466reification as well.
472467
473- Example using ``__dict__ `` from external code:
474-
475- .. code-block :: python
476-
477- # my_module.py
478- import sys
479- lazy import json
480-
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'>
468+ However, calling ``globals() `` or accessing a module's ``__dict__ `` does **not **
469+ trigger reification -- they return the module's dictionary, and accessing lazy
470+ objects through that dictionary still returns lazy proxy objects that need to
471+ be manually reified upon use. A lazy object can be resolved explicitly by
472+ calling the ``resolve `` method. Other, more indirect ways of accessing
473+ arbitrary globals (e.g. inspecting ``frame.f_globals ``) also do **not ** reify
474+ 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,35 +1230,40 @@ 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() ``
1256- method.
1233+ Yes, individual lazy objects can be resolved by calling their ``resolve() ``
1234+ method. Note that accessing a module's ``__dict__ `` or calling ``globals() ``
1235+ does **not ** automatically reify lazy imports -- you'll see the lazy proxy
1236+ objects themselves, which you can then manually resolve if needed.
12571237
12581238What's the difference between ``globals() `` and ``mod.__dict__ `` for lazy imports?
12591239----------------------------------------------------------------------------------
12601240
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:
1241+ Both ``globals() `` and ``mod.__dict__ `` return the module's dictionary without
1242+ reifying lazy imports. Accessing lazy objects through either will yield lazy
1243+ proxy objects. This provides a consistent low-level API for introspection:
12651244
12661245.. code-block :: python
12671246
12681247 # In your module:
12691248 lazy import json
12701249
12711250 g = globals ()
1272- print (type (g[' json' ])) # <class 'LazyImport'> - your problem
1251+ print (type (g[' json' ])) # <class 'LazyImport'>
1252+
1253+ d = __dict__
1254+ print (type (d[' json' ])) # <class 'LazyImport'>
12731255
12741256 # From external code:
12751257 import sys
12761258 mod = sys.modules[' your_module' ]
12771259 d = mod.__dict__
1278- print (type (d[' json' ])) # <class 'module'> - reified for external access
1260+ print (type (d[' json' ])) # <class 'LazyImport'>
12791261
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.
1262+ Both ``globals() `` and ``__dict__ `` expose the raw namespace view without
1263+ implicit side effects. This symmetry makes the behavior predictable: accessing
1264+ the namespace dictionary never triggers imports. If you need to ensure an
1265+ import is resolved, call the ``resolve() `` method explicitly or access the
1266+ attribute directly (e.g., ``json.dumps ``).
12831267
12841268Why not use ``importlib.util.LazyLoader `` instead?
12851269--------------------------------------------------
@@ -1664,6 +1648,59 @@ From the discussion on :pep:`690` it is clear that this is a fairly
16641648contentious idea, although perhaps once we have wide-spread use of lazy
16651649imports this can be reconsidered.
16661650
1651+ Disallowing lazy imports inside ``with `` blocks
1652+ ------------------------------------------------
1653+
1654+ An earlier version of this PEP proposed disallowing ``lazy import `` statements
1655+ inside ``with `` blocks, similar to the restriction on ``try `` blocks. The
1656+ concern was that certain context managers (like ``contextlib.suppress(ImportError) ``)
1657+ could suppress import errors in confusing ways when combined with lazy imports.
1658+
1659+ However, this restriction was rejected because ``with `` statements have much
1660+ broader semantics than ``try/except `` blocks. While ``try/except `` is explicitly
1661+ about catching exceptions, ``with `` blocks are commonly used for resource
1662+ management, temporary state changes, or scoping -- contexts where lazy imports
1663+ work perfectly fine. The ``lazy import `` syntax is explicit enough that
1664+ developers who write it inside a ``with `` block are making an intentional choice,
1665+ aligning with Python's "consenting adults" philosophy. For genuinely problematic
1666+ cases like ``with suppress(ImportError): lazy import foo ``, static analysis
1667+ tools and linters are better suited to catch these patterns than hard language
1668+ restrictions.
1669+
1670+ Additionally, forbidding explicit ``lazy import `` in ``with `` blocks would
1671+ create complex rules for how the global lazy imports flag should behave,
1672+ leading to confusing inconsistencies between explicit and implicit laziness. By
1673+ allowing ``lazy import `` in ``with `` blocks, the rule is simple: the global
1674+ flag affects all module-level imports except those in ``try `` blocks and wild
1675+ card imports, matching exactly what's allowed with explicit syntax.
1676+
1677+ Forcing eager imports in ``with `` blocks under the global flag
1678+ ---------------------------------------------------------------
1679+
1680+ Another rejected idea was to make imports inside ``with `` blocks remain eager
1681+ even when the global lazy imports flag is set to ``"all" ``. The rationale was
1682+ to be conservative: since ``with `` statements can affect how imports behave
1683+ (e.g., by modifying ``sys.path `` or suppressing exceptions), forcing imports to
1684+ remain eager could prevent subtle bugs. However, this would create inconsistent
1685+ behavior where ``lazy import `` is allowed explicitly in ``with `` blocks, but
1686+ normal imports remain eager when the global flag is enabled. This inconsistency
1687+ between explicit and implicit laziness is confusing and hard to explain.
1688+
1689+ The simpler, more consistent rule is that the global flag affects imports
1690+ everywhere that explicit ``lazy import `` syntax is allowed. This avoids having
1691+ three different sets of rules (explicit syntax, global flag behavior, and filter
1692+ mechanism) and instead provides two: explicit syntax rules match what the global
1693+ flag affects, and the filter mechanism provides escape hatches for edge cases.
1694+ For users who need fine-grained control, the filter mechanism
1695+ (``sys.set_lazy_imports_filter() ``) already provides a way to exclude specific
1696+ imports or patterns. Additionally, there's no inverse operation: if the global
1697+ flag forces imports eager in ``with `` blocks but a user wants them lazy, there's
1698+ no way to override it, creating an asymmetry.
1699+
1700+ In summary: imports in ``with `` blocks behave consistently whether marked
1701+ explicitly with ``lazy import `` or implicitly via the global flag, creating a
1702+ simple rule that's easy to explain and reason about.
1703+
16671704Modification of the dict object
16681705-------------------------------
16691706
@@ -1868,55 +1905,53 @@ from a real dict in almost all cases, which is extremely difficult to achieve
18681905correctly. Any deviation from true dict behavior would be a source of subtle
18691906bugs.
18701907
1871- Reifying lazy imports when ``globals() `` is called
1872- ---------------------------------------------------
1908+ Automatically reifying on `` __dict__ `` or ``globals() `` access
1909+ --------------------------------------------------------------
18731910
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:
1911+ Three options were considered for how ``globals() `` and ``mod.__dict__ `` should
1912+ behave with lazy imports:
19071913
190819141. Calling ``globals() `` or ``mod.__dict__ `` traverses and resolves all lazy
19091915 objects before returning.
191019162. Calling ``globals() `` or ``mod.__dict__ `` returns the dictionary with lazy
1911- objects present.
1917+ objects present (chosen) .
191219183. Calling ``globals() `` returns the dictionary with lazy objects, but
19131919 ``mod.__dict__ `` reifies everything.
19141920
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.
1921+ We chose option 2: both ``globals() `` and ``__dict__ `` return the raw
1922+ namespace dictionary without triggering reification. This provides a clean,
1923+ predictable model where low-level introspection APIs don't trigger side
1924+ effects.
1925+
1926+ **Reasons for this choice: **
1927+
1928+ **Consistency and symmetry **: Having ``globals() `` and ``__dict__ `` behave
1929+ identically creates a simple mental model: both expose the raw namespace view.
1930+ This symmetry is easy to explain and predict. The alternative (option 3) would
1931+ create a confusing distinction where accessing the same dictionary through
1932+ different paths yields different behavior.
1933+
1934+ **Low-level APIs shouldn't trigger side effects **: Both ``globals() `` and
1935+ ``__dict__ `` are low-level introspection APIs. Making them automatically trigger
1936+ imports would be surprising and potentially expensive. Code that inspects
1937+ namespaces generally shouldn't cause module loading as a side effect.
1938+
1939+ **Real-world experience **: When implementing lazy imports in parts of the
1940+ standard library (such as the traceback module), it quickly became clear that
1941+ automatic reification on ``__dict__ `` access was cumbersome and not the right
1942+ thing to do. Introspection code that needs to reason about module contents
1943+ shouldn't be forced into loading modules it's only examining.
1944+
1945+ **Explicit is better than implicit **: If code needs lazy imports resolved, it
1946+ can call ``resolve() `` explicitly or access the attributes directly. This puts
1947+ the control in the hands of the code that actually needs the imports loaded.
1948+
1949+ Option 1 (always reifying) was rejected because it would make ``globals() ``
1950+ and ``__dict__ `` access surprisingly expensive and would make it impossible to
1951+ introspect the lazy state of a module. Option 3 was initially considered to
1952+ "protect" external code from seeing lazy objects, but real-world usage showed
1953+ this created more problems than it solved, particularly for stdlib code that
1954+ needs to introspect modules without triggering side effects.
19201955
19211956Acknowledgements
19221957================
0 commit comments