Skip to content

Commit 3350c41

Browse files
committed
gh-149642: Fix interaction between exec and lazy_imports=all
1 parent bc1be4f commit 3350c41

3 files changed

Lines changed: 68 additions & 3 deletions

File tree

Lib/test/test_lazy_import/__init__.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,15 @@ def f():
281281
f()
282282
self.assertIn("only allowed at module level", str(cm.exception))
283283

284+
def test_lazy_import_exec_in_class(self):
285+
"""lazy import via exec() inside a class should raise SyntaxError."""
286+
# exec() inside a class body also has non-module-level locals.
287+
with self.assertRaises(SyntaxError) as cm:
288+
class C:
289+
exec("lazy import json")
290+
291+
self.assertIn("only allowed at module level", str(cm.exception))
292+
284293
@support.requires_subprocess()
285294
def test_lazy_import_exec_at_module_level(self):
286295
"""lazy import via exec() at module level should work."""
@@ -332,6 +341,50 @@ def test_eager_import_func(self):
332341
f = test.test_lazy_import.data.eager_import_func.f
333342
self.assertEqual(type(f()), type(sys))
334343

344+
def test_exec_import_func(self):
345+
"""Implicit lazy imports via exec() inside functions should be eager."""
346+
sys.set_lazy_imports("all")
347+
348+
def f():
349+
exec("import test.test_lazy_import.data.basic2")
350+
351+
f()
352+
self.assertIn("test.test_lazy_import.data.basic2", sys.modules)
353+
354+
def test_exec_import_func_with_lazy_modules(self):
355+
"""__lazy_modules__ should not make exec() imports lazy inside functions."""
356+
globals()["__lazy_modules__"] = ["test.test_lazy_import.data.basic2"]
357+
try:
358+
def f():
359+
exec("import test.test_lazy_import.data.basic2")
360+
361+
f()
362+
self.assertIn("test.test_lazy_import.data.basic2", sys.modules)
363+
finally:
364+
del globals()["__lazy_modules__"]
365+
366+
def test_exec_import_class(self):
367+
"""Implicit lazy imports via exec() inside classes should be eager."""
368+
sys.set_lazy_imports("all")
369+
370+
class C:
371+
exec("import test.test_lazy_import.data.basic2")
372+
373+
self.assertIsNotNone(C)
374+
self.assertIn("test.test_lazy_import.data.basic2", sys.modules)
375+
376+
def test_exec_import_class_with_lazy_modules(self):
377+
"""__lazy_modules__ should not make exec() imports lazy inside classes."""
378+
globals()["__lazy_modules__"] = ["test.test_lazy_import.data.basic2"]
379+
try:
380+
class C:
381+
exec("import test.test_lazy_import.data.basic2")
382+
383+
self.assertIsNotNone(C)
384+
self.assertIn("test.test_lazy_import.data.basic2", sys.modules)
385+
finally:
386+
del globals()["__lazy_modules__"]
387+
335388

336389
class WithStatementTests(unittest.TestCase):
337390
"""Tests for lazy imports in with statement context."""
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Allow imports inside ``exec()`` calls within functions under
2+
``PYTHON_LAZY_IMPORTS=all``.

Python/ceval.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3059,25 +3059,35 @@ check_lazy_import_compatibility(PyThreadState *tstate, PyObject *globals,
30593059
return res;
30603060
}
30613061

3062+
static int
3063+
is_lazy_import_module_level(void)
3064+
{
3065+
_PyInterpreterFrame *frame = _PyEval_GetFrame();
3066+
return frame != NULL && frame->f_globals == frame->f_locals;
3067+
}
3068+
30623069
PyObject *
30633070
_PyEval_LazyImportName(PyThreadState *tstate, PyObject *builtins,
30643071
PyObject *globals, PyObject *locals, PyObject *name,
30653072
PyObject *fromlist, PyObject *level, int lazy)
30663073
{
30673074
PyObject *res = NULL;
3075+
PyImport_LazyImportsMode mode = PyImport_GetLazyImportsMode();
30683076
// Check if global policy overrides the local syntax
3069-
switch (PyImport_GetLazyImportsMode()) {
3077+
switch (mode) {
30703078
case PyImport_LAZY_NONE:
30713079
lazy = 0;
30723080
break;
30733081
case PyImport_LAZY_ALL:
3074-
lazy = 1;
3082+
if (!lazy) {
3083+
lazy = is_lazy_import_module_level();
3084+
}
30753085
break;
30763086
case PyImport_LAZY_NORMAL:
30773087
break;
30783088
}
30793089

3080-
if (!lazy && PyImport_GetLazyImportsMode() != PyImport_LAZY_NONE) {
3090+
if (!lazy && mode != PyImport_LAZY_NONE && is_lazy_import_module_level()) {
30813091
// See if __lazy_modules__ forces this to be lazy.
30823092
lazy = check_lazy_import_compatibility(tstate, globals, name, level);
30833093
if (lazy < 0) {

0 commit comments

Comments
 (0)