https://github.com/python/cpython/commit/15a597e9ba0dd79bf953e181251788782fff6164
commit: 15a597e9ba0dd79bf953e181251788782fff6164
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-05-13T03:36:32Z
summary:

[3.15] gh-149642: Fix interaction between exec and lazy_imports=all (GH-149643) 
(#149749)

gh-149642: Fix interaction between exec and lazy_imports=all (GH-149643)
(cherry picked from commit 4087ff859958abc897711b501bb66dc308890ba5)

Co-authored-by: Jelle Zijlstra <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-05-10-07-42-36.gh-issue-149642.6ZksML.rst
M Lib/test/test_lazy_import/__init__.py
M Python/ceval.c

diff --git a/Lib/test/test_lazy_import/__init__.py 
b/Lib/test/test_lazy_import/__init__.py
index ea534a8ee5b981..5d770eeae07a15 100644
--- a/Lib/test/test_lazy_import/__init__.py
+++ b/Lib/test/test_lazy_import/__init__.py
@@ -301,6 +301,15 @@ def f():
             f()
         self.assertIn("only allowed at module level", str(cm.exception))
 
+    def test_lazy_import_exec_in_class(self):
+        """lazy import via exec() inside a class should raise SyntaxError."""
+        # exec() inside a class body also has non-module-level locals.
+        with self.assertRaises(SyntaxError) as cm:
+            class C:
+                exec("lazy import json")
+
+        self.assertIn("only allowed at module level", str(cm.exception))
+
     @support.requires_subprocess()
     def test_lazy_import_exec_at_module_level(self):
         """lazy import via exec() at module level should work."""
@@ -352,6 +361,50 @@ def test_eager_import_func(self):
         f = test.test_lazy_import.data.eager_import_func.f
         self.assertEqual(type(f()), type(sys))
 
+    def test_exec_import_func(self):
+        """Implicit lazy imports via exec() inside functions should be 
eager."""
+        sys.set_lazy_imports("all")
+
+        def f():
+            exec("import test.test_lazy_import.data.basic2")
+
+        f()
+        self.assertIn("test.test_lazy_import.data.basic2", sys.modules)
+
+    def test_exec_import_func_with_lazy_modules(self):
+        """__lazy_modules__ should not make exec() imports lazy inside 
functions."""
+        globals()["__lazy_modules__"] = ["test.test_lazy_import.data.basic2"]
+        try:
+            def f():
+                exec("import test.test_lazy_import.data.basic2")
+
+            f()
+            self.assertIn("test.test_lazy_import.data.basic2", sys.modules)
+        finally:
+            del globals()["__lazy_modules__"]
+
+    def test_exec_import_class(self):
+        """Implicit lazy imports via exec() inside classes should be eager."""
+        sys.set_lazy_imports("all")
+
+        class C:
+            exec("import test.test_lazy_import.data.basic2")
+
+        self.assertIsNotNone(C)
+        self.assertIn("test.test_lazy_import.data.basic2", sys.modules)
+
+    def test_exec_import_class_with_lazy_modules(self):
+        """__lazy_modules__ should not make exec() imports lazy inside 
classes."""
+        globals()["__lazy_modules__"] = ["test.test_lazy_import.data.basic2"]
+        try:
+            class C:
+                exec("import test.test_lazy_import.data.basic2")
+
+            self.assertIsNotNone(C)
+            self.assertIn("test.test_lazy_import.data.basic2", sys.modules)
+        finally:
+            del globals()["__lazy_modules__"]
+
 
 class WithStatementTests(unittest.TestCase):
     """Tests for lazy imports in with statement context."""
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-10-07-42-36.gh-issue-149642.6ZksML.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-10-07-42-36.gh-issue-149642.6ZksML.rst
new file mode 100644
index 00000000000000..815a084db69d8d
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-10-07-42-36.gh-issue-149642.6ZksML.rst
@@ -0,0 +1,2 @@
+Allow imports inside ``exec()`` calls within functions under
+``PYTHON_LAZY_IMPORTS=all``.
diff --git a/Python/ceval.c b/Python/ceval.c
index 060e948e6b01c9..a080ae42b93766 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -3059,25 +3059,35 @@ check_lazy_import_compatibility(PyThreadState *tstate, 
PyObject *globals,
     return res;
 }
 
+static int
+is_lazy_import_module_level(void)
+{
+    _PyInterpreterFrame *frame = _PyEval_GetFrame();
+    return frame != NULL && frame->f_globals == frame->f_locals;
+}
+
 PyObject *
 _PyEval_LazyImportName(PyThreadState *tstate, PyObject *builtins,
                        PyObject *globals, PyObject *locals, PyObject *name,
                        PyObject *fromlist, PyObject *level, int lazy)
 {
     PyObject *res = NULL;
+    PyImport_LazyImportsMode mode = PyImport_GetLazyImportsMode();
     // Check if global policy overrides the local syntax
-    switch (PyImport_GetLazyImportsMode()) {
+    switch (mode) {
         case PyImport_LAZY_NONE:
             lazy = 0;
             break;
         case PyImport_LAZY_ALL:
-            lazy = 1;
+            if (!lazy) {
+                lazy = is_lazy_import_module_level();
+            }
             break;
         case PyImport_LAZY_NORMAL:
             break;
     }
 
-    if (!lazy && PyImport_GetLazyImportsMode() != PyImport_LAZY_NONE) {
+    if (!lazy && mode != PyImport_LAZY_NONE && is_lazy_import_module_level()) {
         // See if __lazy_modules__ forces this to be lazy.
         lazy = check_lazy_import_compatibility(tstate, globals, name, level);
         if (lazy < 0) {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to