Skip to content

Commit 6d7c87a

Browse files
committed
Reify lazy objects when accessed via the module object
1 parent 058bc6e commit 6d7c87a

File tree

6 files changed

+101
-12
lines changed

6 files changed

+101
-12
lines changed

Include/internal/pycore_moduleobject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ typedef struct {
2424
PyObject *md_weaklist;
2525
// for logging purposes after md_dict is cleared
2626
PyObject *md_name;
27+
// module version we last checked for lazy values
28+
uint32_t m_dict_version;
2729
#ifdef Py_GIL_DISABLED
2830
void *md_gil;
2931
#endif

Lib/test/test_import/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2629,7 +2629,30 @@ def test_compatibility_mode_relative(self):
26292629
self.fail('lazy import failed')
26302630

26312631
self.assertFalse("test.test_import.data.lazy_imports.basic2" in sys.modules)
2632+
2633+
def test_modules_dict(self):
2634+
try:
2635+
import test.test_import.data.lazy_imports.modules_dict
2636+
except ImportError as e:
2637+
self.fail('lazy import failed')
2638+
2639+
self.assertTrue("test.test_import.data.lazy_imports.basic2" in sys.modules)
2640+
2641+
def test_modules_geatattr(self):
2642+
try:
2643+
import test.test_import.data.lazy_imports.modules_getattr
2644+
except ImportError as e:
2645+
self.fail('lazy import failed')
2646+
2647+
self.assertTrue("test.test_import.data.lazy_imports.basic2" in sys.modules)
2648+
2649+
def test_modules_geatattr_other(self):
2650+
try:
2651+
import test.test_import.data.lazy_imports.modules_getattr_other
2652+
except ImportError as e:
2653+
self.fail('lazy import failed')
26322654

2655+
self.assertFalse("test.test_import.data.lazy_imports.basic2" in sys.modules)
26332656

26342657

26352658
class TestSinglePhaseSnapshot(ModuleSnapshot):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
lazy import test.test_import.data.lazy_imports.basic2 as basic2
2+
3+
import sys
4+
mod = sys.modules[__name__]
5+
x = mod.__dict__
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
lazy import test.test_import.data.lazy_imports.basic2 as basic2
2+
3+
import sys
4+
mod = sys.modules[__name__]
5+
x = mod.basic2
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
lazy import test.test_import.data.lazy_imports.basic2 as basic2
2+
3+
import sys
4+
mod = sys.modules[__name__]
5+
x = mod.__name__

Objects/moduleobject.c

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "pycore_fileutils.h" // _Py_wgetcwd
77
#include "pycore_import.h" // _PyImport_GetNextModuleIndex()
88
#include "pycore_interp.h" // PyInterpreterState.importlib
9+
#include "pycore_lazyimportobject.h" // _PyLazyImportObject_Check()
910
#include "pycore_long.h" // _PyLong_GetOne()
1011
#include "pycore_modsupport.h" // _PyModule_CreateInitialized()
1112
#include "pycore_moduleobject.h" // _PyModule_GetDef()
@@ -22,12 +23,6 @@
2223
(assert(PyModule_Check(op)), _Py_CAST(PyModuleObject*, (op)))
2324

2425

25-
static PyMemberDef module_members[] = {
26-
{"__dict__", _Py_T_OBJECT, offsetof(PyModuleObject, md_dict), Py_READONLY},
27-
{0}
28-
};
29-
30-
3126
PyTypeObject PyModuleDef_Type = {
3227
PyVarObject_HEAD_INIT(&PyType_Type, 0)
3328
"moduledef", /* tp_name */
@@ -1048,6 +1043,18 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
10481043
PyObject *attr, *mod_name, *getattr;
10491044
attr = _PyObject_GenericGetAttrWithDict((PyObject *)m, name, NULL, suppress);
10501045
if (attr) {
1046+
if (PyLazyImport_CheckExact(attr)) {
1047+
PyObject *new_value = _PyImport_LoadLazyImportTstate(PyThreadState_GET(), attr);
1048+
if (new_value == NULL) {
1049+
return NULL;
1050+
} else if (PyDict_SetItem(m->md_dict, name, new_value) < 0) {
1051+
Py_DECREF(new_value);
1052+
Py_DECREF(attr);
1053+
return NULL;
1054+
}
1055+
Py_DECREF(attr);
1056+
return new_value;
1057+
}
10511058
return attr;
10521059
}
10531060
if (suppress == 1) {
@@ -1273,7 +1280,7 @@ static PyMethodDef module_methods[] = {
12731280
};
12741281

12751282
static PyObject *
1276-
module_get_dict(PyModuleObject *m)
1283+
module_load_dict(PyModuleObject *m)
12771284
{
12781285
PyObject *dict = PyObject_GetAttr((PyObject *)m, &_Py_ID(__dict__));
12791286
if (dict == NULL) {
@@ -1292,7 +1299,7 @@ module_get_annotate(PyObject *self, void *Py_UNUSED(ignored))
12921299
{
12931300
PyModuleObject *m = _PyModule_CAST(self);
12941301

1295-
PyObject *dict = module_get_dict(m);
1302+
PyObject *dict = module_load_dict(m);
12961303
if (dict == NULL) {
12971304
return NULL;
12981305
}
@@ -1317,7 +1324,7 @@ module_set_annotate(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
13171324
return -1;
13181325
}
13191326

1320-
PyObject *dict = module_get_dict(m);
1327+
PyObject *dict = module_load_dict(m);
13211328
if (dict == NULL) {
13221329
return -1;
13231330
}
@@ -1347,7 +1354,7 @@ module_get_annotations(PyObject *self, void *Py_UNUSED(ignored))
13471354
{
13481355
PyModuleObject *m = _PyModule_CAST(self);
13491356

1350-
PyObject *dict = module_get_dict(m);
1357+
PyObject *dict = module_load_dict(m);
13511358
if (dict == NULL) {
13521359
return NULL;
13531360
}
@@ -1419,7 +1426,7 @@ module_set_annotations(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)
14191426
{
14201427
PyModuleObject *m = _PyModule_CAST(self);
14211428

1422-
PyObject *dict = module_get_dict(m);
1429+
PyObject *dict = module_load_dict(m);
14231430
if (dict == NULL) {
14241431
return -1;
14251432
}
@@ -1448,10 +1455,52 @@ module_set_annotations(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)
14481455
return ret;
14491456
}
14501457

1458+
static PyObject *
1459+
module_get_dict(PyObject *mod, void *Py_UNUSED(ignored))
1460+
{
1461+
PyModuleObject *self = (PyModuleObject *)mod;
1462+
PyThreadState *tstate = PyThreadState_GET();
1463+
Py_BEGIN_CRITICAL_SECTION(self->md_dict);
1464+
uint32_t version = _PyDict_GetKeysVersionForCurrentState(tstate->interp,
1465+
(PyDictObject *)self->md_dict);
1466+
// Check if the dict has been updated since we last checked to see if
1467+
// it has lazy values.
1468+
if (self->m_dict_version != version || version == 0) {
1469+
// Scan for lazy values...
1470+
bool retry;
1471+
do {
1472+
retry = false;
1473+
Py_ssize_t pos = 0;
1474+
PyObject *key, *value;
1475+
while (PyDict_Next(self->md_dict, &pos, &key, &value)) {
1476+
if (PyLazyImport_CheckExact(value)) {
1477+
PyObject *new_value = _PyImport_LoadLazyImportTstate(tstate, value);
1478+
if (new_value == NULL) {
1479+
return NULL;
1480+
}
1481+
if (PyDict_SetItem(self->md_dict, key, new_value) < 0) {
1482+
Py_DECREF(new_value);
1483+
return NULL;
1484+
}
1485+
if (!PyLazyImport_CheckExact(value)) {
1486+
// Only force a retry if we actually made forward progress
1487+
retry = true;
1488+
}
1489+
Py_DECREF(new_value);
1490+
}
1491+
}
1492+
} while(retry);
1493+
self->m_dict_version = _PyDict_GetKeysVersionForCurrentState(tstate->interp,
1494+
(PyDictObject *)self->md_dict);
1495+
}
1496+
Py_END_CRITICAL_SECTION();
1497+
return Py_NewRef(self->md_dict);
1498+
}
14511499

14521500
static PyGetSetDef module_getsets[] = {
14531501
{"__annotations__", module_get_annotations, module_set_annotations},
14541502
{"__annotate__", module_get_annotate, module_set_annotate},
1503+
{"__dict__", (getter)module_get_dict, NULL},
14551504
{NULL}
14561505
};
14571506

@@ -1485,7 +1534,7 @@ PyTypeObject PyModule_Type = {
14851534
0, /* tp_iter */
14861535
0, /* tp_iternext */
14871536
module_methods, /* tp_methods */
1488-
module_members, /* tp_members */
1537+
0, /* tp_members */
14891538
module_getsets, /* tp_getset */
14901539
0, /* tp_base */
14911540
0, /* tp_dict */

0 commit comments

Comments
 (0)