Skip to content

Commit 3e339ae

Browse files
committed
pythongh-95754: Better AttributeError on partially initialised module
1 parent e44f194 commit 3e339ae

File tree

3 files changed

+24
-2
lines changed

3 files changed

+24
-2
lines changed

Lib/test/test_import/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,6 +1629,14 @@ def test_circular_from_import(self):
16291629
str(cm.exception),
16301630
)
16311631

1632+
def test_circular_import(self):
1633+
with self.assertRaisesRegex(
1634+
AttributeError,
1635+
r"partially initialized module 'test.test_import.data.circular_imports.import_cycle' "
1636+
r"from '.*' has no attribute 'some_attribute' \(most likely due to a circular import\)"
1637+
):
1638+
import test.test_import.data.circular_imports.import_cycle
1639+
16321640
def test_absolute_circular_submodule(self):
16331641
with self.assertRaises(AttributeError) as cm:
16341642
import test.test_import.data.circular_imports.subpkg2.parent
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import test.test_import.data.circular_imports.import_cycle as m
2+
3+
m.some_attribute

Objects/moduleobject.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ PyObject*
799799
_Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
800800
{
801801
// When suppress=1, this function suppresses AttributeError.
802-
PyObject *attr, *mod_name, *getattr;
802+
PyObject *attr, *mod_name, *getattr, *origin;
803803
attr = _PyObject_GenericGetAttrWithDict((PyObject *)m, name, NULL, suppress);
804804
if (attr) {
805805
return attr;
@@ -841,11 +841,22 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
841841
}
842842
if (suppress != 1) {
843843
if (_PyModuleSpec_IsInitializing(spec)) {
844-
PyErr_Format(PyExc_AttributeError,
844+
origin = PyObject_GetAttr(spec, &_Py_ID(origin));
845+
if (origin == NULL || origin == Py_None) {
846+
PyErr_Format(PyExc_AttributeError,
845847
"partially initialized "
846848
"module '%U' has no attribute '%U' "
847849
"(most likely due to a circular import)",
848850
mod_name, name);
851+
}
852+
else {
853+
PyErr_Format(PyExc_AttributeError,
854+
"partially initialized "
855+
"module '%U' from '%U' has no attribute '%U' "
856+
"(most likely due to a circular import)",
857+
mod_name, origin, name);
858+
}
859+
Py_XDECREF(origin);
849860
}
850861
else if (_PyModuleSpec_IsUninitializedSubmodule(spec, name)) {
851862
PyErr_Format(PyExc_AttributeError,

0 commit comments

Comments
 (0)