Reify lazy objects when accessed via the module object

This commit is contained in:
Dino Viehland 2025-09-22 08:45:00 -07:00
parent 058bc6e884
commit 6d7c87a3fe
6 changed files with 101 additions and 12 deletions

View file

@ -6,6 +6,7 @@
#include "pycore_fileutils.h" // _Py_wgetcwd
#include "pycore_import.h" // _PyImport_GetNextModuleIndex()
#include "pycore_interp.h" // PyInterpreterState.importlib
#include "pycore_lazyimportobject.h" // _PyLazyImportObject_Check()
#include "pycore_long.h" // _PyLong_GetOne()
#include "pycore_modsupport.h" // _PyModule_CreateInitialized()
#include "pycore_moduleobject.h" // _PyModule_GetDef()
@ -22,12 +23,6 @@
(assert(PyModule_Check(op)), _Py_CAST(PyModuleObject*, (op)))
static PyMemberDef module_members[] = {
{"__dict__", _Py_T_OBJECT, offsetof(PyModuleObject, md_dict), Py_READONLY},
{0}
};
PyTypeObject PyModuleDef_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"moduledef", /* tp_name */
@ -1048,6 +1043,18 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
PyObject *attr, *mod_name, *getattr;
attr = _PyObject_GenericGetAttrWithDict((PyObject *)m, name, NULL, suppress);
if (attr) {
if (PyLazyImport_CheckExact(attr)) {
PyObject *new_value = _PyImport_LoadLazyImportTstate(PyThreadState_GET(), attr);
if (new_value == NULL) {
return NULL;
} else if (PyDict_SetItem(m->md_dict, name, new_value) < 0) {
Py_DECREF(new_value);
Py_DECREF(attr);
return NULL;
}
Py_DECREF(attr);
return new_value;
}
return attr;
}
if (suppress == 1) {
@ -1273,7 +1280,7 @@ static PyMethodDef module_methods[] = {
};
static PyObject *
module_get_dict(PyModuleObject *m)
module_load_dict(PyModuleObject *m)
{
PyObject *dict = PyObject_GetAttr((PyObject *)m, &_Py_ID(__dict__));
if (dict == NULL) {
@ -1292,7 +1299,7 @@ module_get_annotate(PyObject *self, void *Py_UNUSED(ignored))
{
PyModuleObject *m = _PyModule_CAST(self);
PyObject *dict = module_get_dict(m);
PyObject *dict = module_load_dict(m);
if (dict == NULL) {
return NULL;
}
@ -1317,7 +1324,7 @@ module_set_annotate(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
return -1;
}
PyObject *dict = module_get_dict(m);
PyObject *dict = module_load_dict(m);
if (dict == NULL) {
return -1;
}
@ -1347,7 +1354,7 @@ module_get_annotations(PyObject *self, void *Py_UNUSED(ignored))
{
PyModuleObject *m = _PyModule_CAST(self);
PyObject *dict = module_get_dict(m);
PyObject *dict = module_load_dict(m);
if (dict == NULL) {
return NULL;
}
@ -1419,7 +1426,7 @@ module_set_annotations(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)
{
PyModuleObject *m = _PyModule_CAST(self);
PyObject *dict = module_get_dict(m);
PyObject *dict = module_load_dict(m);
if (dict == NULL) {
return -1;
}
@ -1448,10 +1455,52 @@ module_set_annotations(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)
return ret;
}
static PyObject *
module_get_dict(PyObject *mod, void *Py_UNUSED(ignored))
{
PyModuleObject *self = (PyModuleObject *)mod;
PyThreadState *tstate = PyThreadState_GET();
Py_BEGIN_CRITICAL_SECTION(self->md_dict);
uint32_t version = _PyDict_GetKeysVersionForCurrentState(tstate->interp,
(PyDictObject *)self->md_dict);
// Check if the dict has been updated since we last checked to see if
// it has lazy values.
if (self->m_dict_version != version || version == 0) {
// Scan for lazy values...
bool retry;
do {
retry = false;
Py_ssize_t pos = 0;
PyObject *key, *value;
while (PyDict_Next(self->md_dict, &pos, &key, &value)) {
if (PyLazyImport_CheckExact(value)) {
PyObject *new_value = _PyImport_LoadLazyImportTstate(tstate, value);
if (new_value == NULL) {
return NULL;
}
if (PyDict_SetItem(self->md_dict, key, new_value) < 0) {
Py_DECREF(new_value);
return NULL;
}
if (!PyLazyImport_CheckExact(value)) {
// Only force a retry if we actually made forward progress
retry = true;
}
Py_DECREF(new_value);
}
}
} while(retry);
self->m_dict_version = _PyDict_GetKeysVersionForCurrentState(tstate->interp,
(PyDictObject *)self->md_dict);
}
Py_END_CRITICAL_SECTION();
return Py_NewRef(self->md_dict);
}
static PyGetSetDef module_getsets[] = {
{"__annotations__", module_get_annotations, module_set_annotations},
{"__annotate__", module_get_annotate, module_set_annotate},
{"__dict__", (getter)module_get_dict, NULL},
{NULL}
};
@ -1485,7 +1534,7 @@ PyTypeObject PyModule_Type = {
0, /* tp_iter */
0, /* tp_iternext */
module_methods, /* tp_methods */
module_members, /* tp_members */
0, /* tp_members */
module_getsets, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */