Skip to content

Commit 1f3c267

Browse files
authored
gh-149321: Remove lazy_imports=none startup mode (#149389)
1 parent 79088e0 commit 1f3c267

18 files changed

Lines changed: 148208 additions & 217 deletions

File tree

Doc/c-api/import.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -393,11 +393,6 @@ Importing Modules
393393
394394
Make all imports lazy by default.
395395
396-
.. c:enumerator:: PyImport_LAZY_NONE
397-
398-
Disable lazy imports entirely. Even explicit ``lazy`` statements become
399-
eager imports.
400-
401396
.. versionadded:: 3.15
402397
403398
.. c:function:: PyObject* PyImport_CreateModuleFromInitfunc(PyObject *spec, PyObject* (*initfunc)(void))

Doc/library/sys.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -919,8 +919,6 @@ always available. Unless explicitly noted otherwise, all variables are read-only
919919
* ``"normal"``: Only imports explicitly marked with the ``lazy`` keyword
920920
are lazy
921921
* ``"all"``: All top-level imports are potentially lazy
922-
* ``"none"``: All lazy imports are suppressed (even explicitly marked
923-
ones)
924922

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

@@ -1757,8 +1755,6 @@ always available. Unless explicitly noted otherwise, all variables are read-only
17571755
* ``"normal"``: Only imports explicitly marked with the ``lazy`` keyword
17581756
are lazy
17591757
* ``"all"``: All top-level imports become potentially lazy
1760-
* ``"none"``: All lazy imports are suppressed (even explicitly marked
1761-
ones)
17621758

17631759
This function is intended for advanced users who need to control lazy
17641760
imports across their entire application. Library developers should

Doc/reference/simple_stmts.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -965,10 +965,6 @@ Imports inside functions, class bodies, or
965965
:keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are always eager,
966966
regardless of :attr:`!__lazy_modules__`.
967967

968-
Setting ``-X lazy_imports=none`` (or the :envvar:`PYTHON_LAZY_IMPORTS`
969-
environment variable to ``none``) overrides :attr:`!__lazy_modules__` and
970-
forces all imports to be eager.
971-
972968
.. versionadded:: 3.15
973969

974970
.. _future:

Doc/tools/removed-ids.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ using/configure.html: cmdoption-with-system-libmpdec
1212
# Removed APIs
1313
library/symtable.html: symtable.Class.get_methods
1414
library/sys.html: sys._enablelegacywindowsfsencoding
15+
c-api/import.html: c.PyImport_LazyImportsMode.PyImport_LAZY_NONE

Doc/using/cmdline.rst

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -705,10 +705,9 @@ Miscellaneous options
705705

706706
.. versionadded:: 3.14
707707

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

714713
.. versionadded:: 3.15
@@ -1413,10 +1412,9 @@ conflict.
14131412

14141413
.. envvar:: PYTHON_LAZY_IMPORTS
14151414

1416-
Controls lazy import behavior. Accepts three values: ``all`` makes all
1417-
imports lazy by default, ``none`` disables lazy imports entirely (even
1418-
explicit ``lazy`` statements become eager), and ``normal`` (the default)
1419-
respects the ``lazy`` keyword in source code.
1415+
Controls lazy import behavior. Accepts two values: ``all`` makes all
1416+
imports lazy by default, and ``normal`` (the default) respects the
1417+
``lazy`` keyword in source code.
14201418

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

Doc/whatsnew/3.15.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,10 @@ making it straightforward to diagnose and debug the failure.
157157
For cases where you want to enable lazy loading globally without modifying
158158
source code, Python provides the :option:`-X lazy_imports <-X>` command-line
159159
option and the :envvar:`PYTHON_LAZY_IMPORTS` environment variable. Both
160-
accept three values: ``all`` makes all imports lazy by default, ``none``
161-
disables lazy imports entirely (even explicit ``lazy`` statements become
162-
eager), and ``normal`` (the default) respects the ``lazy`` keyword in source
163-
code. The :func:`sys.set_lazy_imports` and :func:`sys.get_lazy_imports`
164-
functions allow changing and querying this mode at runtime.
160+
accept two values: ``all`` makes all imports lazy by default, and ``normal``
161+
(the default) respects the ``lazy`` keyword in source code. The
162+
:func:`sys.set_lazy_imports` and :func:`sys.get_lazy_imports` functions allow
163+
changing and querying this mode at runtime.
165164

166165
For more selective control, :func:`sys.set_lazy_imports_filter` accepts a
167166
callable that determines whether a specific module should be loaded lazily.

Include/import.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ PyAPI_FUNC(int) PyImport_AppendInittab(
9090

9191
typedef enum {
9292
PyImport_LAZY_NORMAL,
93-
PyImport_LAZY_ALL,
94-
PyImport_LAZY_NONE
93+
PyImport_LAZY_ALL
9594
} PyImport_LazyImportsMode;
9695

9796
#ifndef Py_LIMITED_API

Lib/test/test_lazy_import/__init__.py

Lines changed: 24 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,10 @@ def tearDown(self):
121121
sys.set_lazy_imports_filter(None)
122122
sys.set_lazy_imports("normal")
123123

124-
def test_global_off(self):
125-
"""Mode 'none' should disable lazy imports entirely."""
126-
import test.test_lazy_import.data.global_off
127-
self.assertIn("test.test_lazy_import.data.basic2", sys.modules)
124+
def test_global_off_rejected(self):
125+
"""Mode 'none' is not supported."""
126+
with self.assertRaises(ValueError):
127+
sys.set_lazy_imports("none")
128128

129129
def test_global_on(self):
130130
"""Mode 'all' should make regular imports lazy."""
@@ -557,9 +557,6 @@ def test_get_lazy_imports_returns_string(self):
557557
sys.set_lazy_imports("all")
558558
self.assertEqual(sys.get_lazy_imports(), "all")
559559

560-
sys.set_lazy_imports("none")
561-
self.assertEqual(sys.get_lazy_imports(), "none")
562-
563560
def test_get_lazy_imports_filter_default(self):
564561
"""get_lazy_imports_filter should return None by default."""
565562
sys.set_lazy_imports_filter(None)
@@ -1104,68 +1101,16 @@ def test_cli_lazy_imports_all_makes_regular_imports_lazy(self):
11041101
self.assertEqual(result.returncode, 0, f"stderr: {result.stderr}")
11051102
self.assertIn("LAZY", result.stdout)
11061103

1107-
def test_cli_lazy_imports_none_forces_all_imports_eager(self):
1108-
"""-X lazy_imports=none should force all imports to be eager."""
1109-
code = textwrap.dedent("""
1110-
import sys
1111-
# Even explicit lazy imports should be eager in 'none' mode
1112-
lazy import json
1113-
if 'json' in sys.modules:
1114-
print("EAGER")
1115-
else:
1116-
print("LAZY")
1117-
""")
1104+
def test_cli_lazy_imports_none_is_rejected(self):
1105+
"""-X lazy_imports=none should be rejected."""
11181106
result = subprocess.run(
1119-
[sys.executable, "-X", "lazy_imports=none", "-c", code],
1107+
[sys.executable, "-X", "lazy_imports=none", "-c", "pass"],
11201108
capture_output=True,
11211109
text=True
11221110
)
1123-
self.assertEqual(result.returncode, 0, f"stderr: {result.stderr}")
1124-
self.assertIn("EAGER", result.stdout)
1125-
1126-
@support.requires_resource("cpu")
1127-
def test_cli_lazy_imports_modes_import_stdlib_modules(self):
1128-
"""-X lazy_imports modes should import available stdlib modules."""
1129-
# Do not smoke-test modules with intentional import-time effects.
1130-
import_side_effect_modules = {"antigravity", "this"}
1131-
importable = []
1132-
1133-
for module in sorted(sys.stdlib_module_names):
1134-
if module in import_side_effect_modules:
1135-
continue
1136-
1137-
with self.subTest(module=module):
1138-
code = f"import {module}; print({module})"
1139-
baseline = subprocess.run(
1140-
[sys.executable, "-I", "-c", code],
1141-
capture_output=True,
1142-
text=True,
1143-
timeout=60,
1144-
)
1145-
if baseline.returncode:
1146-
# sys.stdlib_module_names includes modules for other
1147-
# platforms and optional extension modules not built here.
1148-
continue
1149-
importable.append(module)
1150-
1151-
for mode in ("normal", "none"):
1152-
with self.subTest(module=module, mode=mode):
1153-
result = subprocess.run(
1154-
[
1155-
sys.executable,
1156-
"-I",
1157-
"-X",
1158-
f"lazy_imports={mode}",
1159-
"-c",
1160-
code,
1161-
],
1162-
capture_output=True,
1163-
text=True,
1164-
timeout=60,
1165-
)
1166-
self.assertEqual(result.returncode, 0, result.stderr)
1167-
1168-
self.assertGreater(len(importable), 100)
1111+
self.assertNotEqual(result.returncode, 0)
1112+
self.assertIn("-X lazy_imports: invalid value", result.stderr)
1113+
self.assertIn("expected 'all' or 'normal'", result.stderr)
11691114

11701115
def test_cli_lazy_imports_normal_respects_lazy_keyword_only(self):
11711116
"""-X lazy_imports=normal should respect lazy keyword only."""
@@ -1214,101 +1159,51 @@ def test_env_var_lazy_imports_all_enables_global_lazy(self):
12141159
self.assertEqual(result.returncode, 0, f"stderr: {result.stderr}")
12151160
self.assertIn("LAZY", result.stdout)
12161161

1217-
def test_env_var_lazy_imports_none_disables_all_lazy(self):
1218-
"""PYTHON_LAZY_IMPORTS=none should disable all lazy imports."""
1219-
code = textwrap.dedent("""
1220-
import sys
1221-
lazy import json
1222-
if 'json' in sys.modules:
1223-
print("EAGER")
1224-
else:
1225-
print("LAZY")
1226-
""")
1162+
def test_env_var_lazy_imports_none_is_rejected(self):
1163+
"""PYTHON_LAZY_IMPORTS=none should be rejected."""
12271164
import os
12281165
env = os.environ.copy()
12291166
env["PYTHON_LAZY_IMPORTS"] = "none"
12301167
result = subprocess.run(
1231-
[sys.executable, "-c", code],
1168+
[sys.executable, "-c", "pass"],
12321169
capture_output=True,
12331170
text=True,
12341171
env=env
12351172
)
1236-
self.assertEqual(result.returncode, 0, f"stderr: {result.stderr}")
1237-
self.assertIn("EAGER", result.stdout)
1238-
1239-
def test_cli_lazy_imports_none_disables_dunder_lazy_modules(self):
1240-
"""-X lazy_imports=none should override __lazy_modules__."""
1241-
code = textwrap.dedent("""
1242-
import sys
1243-
__lazy_modules__ = ["json"]
1244-
import json
1245-
if 'json' in sys.modules:
1246-
print("EAGER")
1247-
else:
1248-
print("LAZY")
1249-
""")
1250-
result = subprocess.run(
1251-
[sys.executable, "-X", "lazy_imports=none", "-c", code],
1252-
capture_output=True,
1253-
text=True,
1254-
)
1255-
self.assertEqual(result.returncode, 0, f"stderr: {result.stderr}")
1256-
self.assertIn("EAGER", result.stdout)
1257-
1258-
def test_env_var_lazy_imports_none_disables_dunder_lazy_modules(self):
1259-
"""PYTHON_LAZY_IMPORTS=none should override __lazy_modules__."""
1260-
code = textwrap.dedent("""
1261-
import sys
1262-
__lazy_modules__ = ["json"]
1263-
import json
1264-
if 'json' in sys.modules:
1265-
print("EAGER")
1266-
else:
1267-
print("LAZY")
1268-
""")
1269-
import os
1270-
1271-
env = os.environ.copy()
1272-
env["PYTHON_LAZY_IMPORTS"] = "none"
1273-
result = subprocess.run(
1274-
[sys.executable, "-c", code],
1275-
capture_output=True,
1276-
text=True,
1277-
env=env,
1278-
)
1279-
self.assertEqual(result.returncode, 0, f"stderr: {result.stderr}")
1280-
self.assertIn("EAGER", result.stdout)
1173+
self.assertNotEqual(result.returncode, 0)
1174+
self.assertIn("PYTHON_LAZY_IMPORTS: invalid value", result.stderr)
1175+
self.assertIn("expected 'all' or 'normal'", result.stderr)
12811176

12821177
def test_cli_overrides_env_var(self):
12831178
"""Command-line option should take precedence over environment variable."""
12841179
# PEP 810: -X lazy_imports takes precedence over PYTHON_LAZY_IMPORTS
12851180
code = textwrap.dedent("""
12861181
import sys
1287-
lazy import json
1182+
import json
12881183
if 'json' in sys.modules:
12891184
print("EAGER")
12901185
else:
12911186
print("LAZY")
12921187
""")
12931188
import os
12941189
env = os.environ.copy()
1295-
env["PYTHON_LAZY_IMPORTS"] = "all" # env says all
1190+
env["PYTHON_LAZY_IMPORTS"] = "all" # env says all imports are lazy
12961191
result = subprocess.run(
1297-
[sys.executable, "-X", "lazy_imports=none", "-c", code], # CLI says none
1192+
[sys.executable, "-X", "lazy_imports=normal", "-c", code],
12981193
capture_output=True,
12991194
text=True,
13001195
env=env
13011196
)
13021197
self.assertEqual(result.returncode, 0, f"stderr: {result.stderr}")
1303-
# CLI should win - imports should be eager
1198+
# CLI should win, so a regular import should stay eager.
13041199
self.assertIn("EAGER", result.stdout)
13051200

13061201
def test_sys_set_lazy_imports_overrides_cli(self):
13071202
"""sys.set_lazy_imports() should take precedence over CLI option."""
13081203
code = textwrap.dedent("""
13091204
import sys
1310-
sys.set_lazy_imports("none") # Override CLI
1311-
lazy import json
1205+
sys.set_lazy_imports("normal") # Override CLI
1206+
import json
13121207
if 'json' in sys.modules:
13131208
print("EAGER")
13141209
else:
@@ -2090,9 +1985,10 @@ def tearDown(self):
20901985

20911986
def test_set_matches_sys(self):
20921987
self.assertEqual(_testcapi.PyImport_GetLazyImportsMode(), sys.get_lazy_imports())
2093-
for mode in ("normal", "all", "none"):
1988+
for mode in ("normal", "all"):
20941989
_testcapi.PyImport_SetLazyImportsMode(mode)
20951990
self.assertEqual(_testcapi.PyImport_GetLazyImportsMode(), sys.get_lazy_imports())
1991+
self.assertRaises(ValueError, _testcapi.PyImport_SetLazyImportsMode, "none")
20961992

20971993
def test_filter_matches_sys(self):
20981994
self.assertEqual(_testcapi.PyImport_GetLazyImportsFilter(), sys.get_lazy_imports_filter())

Lib/test/test_lazy_import/data/global_off.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

Misc/NEWS.d/3.15.0a8.rst

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -180,16 +180,6 @@ dealing with contradictions in ``make_bottom``.
180180

181181
..
182182
183-
.. date: 2026-03-24-13-06-52
184-
.. gh-issue: 146369
185-
.. nonce: 6wDI6S
186-
.. section: Core and Builtins
187-
188-
Ensure ``-X lazy_imports=none`` and ``PYTHON_LAZY_IMPORTS=none`` override
189-
:attr:`~module.__lazy_modules__`. Patch by Hugo van Kemenade.
190-
191-
..
192-
193183
.. date: 2026-03-22-19-30-00
194184
.. gh-issue: 146308
195185
.. nonce: AxnRVA

0 commit comments

Comments
 (0)