New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-44717: improve AttributeError on circular imports of submodules #27299
Conversation
This comment has been hidden.
This comment has been hidden.
Signed-off-by: Filipe Laíns <lains@riseup.net>
@FFY00, I refactored your change a little in |
Looks great, thanks! I wasn't sure about always setting the attribute so I just mimicked |
Btw, the tests are failing because you renamed |
Lib/importlib/_bootstrap.py
Outdated
[]) | ||
parent_spec._uninitialized_submodules = _uninitialized + [child] | ||
if parent_spec: | ||
# Temporarily add currently imported child to parent's |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hum, shouldn't it more correct to say "child we are going to import" instead of "currently imported child"? As the child isn't actually imported, we are going to try to import it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error occurs when the child (let's say a.b.c
) tries to import the parent (import a.b
), but the parent also imports the child (import a.b.c
), which results in the child not importing the parent, just the parent's parent (a
) and not setting the parent as an attribute (a.b
will not be set). We want to be able to flag that error when we import the child (a.b.c
) and provide a nicer message if it tries to access the parent (a.b
, which is not set, only a
). Very confusing, I know
Lib/importlib/_bootstrap.py
Outdated
@@ -361,6 +361,7 @@ def __init__(self, name, loader, *, origin=None, loader_state=None, | |||
self.origin = origin | |||
self.loader_state = loader_state | |||
self.submodule_search_locations = [] if is_package else None | |||
self.uninitialized_submodules = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though: This should better be a set, no? That way you can use PySet_Contains
in the C code, which is not linear.
On the other hand, we don't expect this to be performance-critical and someone could change it so PySet_Contains
can fail without a pre-check, which complicates the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I don't really have any preference, as this is something that will not really matter much. Let me know if you want me to change it to set.
Objects/moduleobject.c
Outdated
_PyModuleSpec_IsUninitializedSubmodule(PyObject *spec, PyObject *name) | ||
{ | ||
if (spec != NULL) { | ||
_Py_IDENTIFIER(_uninitialized_submodules); | ||
PyObject *value = _PyObject_GetAttrId(spec, | ||
&PyId__uninitialized_submodules); | ||
if (value != NULL) { | ||
int is_uninitialized = PySequence_Contains(value, name); | ||
Py_DECREF(value); | ||
if (is_uninitialized >= 0) { | ||
return is_uninitialized; | ||
} | ||
} | ||
} | ||
PyErr_Clear(); | ||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to avoid nesting the conditionals so every case is more clear:
_PyModuleSpec_IsUninitializedSubmodule(PyObject *spec, PyObject *name) | |
{ | |
if (spec != NULL) { | |
_Py_IDENTIFIER(_uninitialized_submodules); | |
PyObject *value = _PyObject_GetAttrId(spec, | |
&PyId__uninitialized_submodules); | |
if (value != NULL) { | |
int is_uninitialized = PySequence_Contains(value, name); | |
Py_DECREF(value); | |
if (is_uninitialized >= 0) { | |
return is_uninitialized; | |
} | |
} | |
} | |
PyErr_Clear(); | |
return 0; | |
} | |
_PyModuleSpec_IsUninitializedSubmodule(PyObject *spec, PyObject *name) | |
{ | |
if (spec == NULL) { | |
goto exit; | |
} | |
_Py_IDENTIFIER(_uninitialized_submodules); | |
PyObject *value = _PyObject_GetAttrId(spec, &PyId__uninitialized_submodules); | |
if (value == NULL) { | |
goto error; | |
} | |
int is_uninitialized = PySequence_Contains(value, name); | |
Py_DECREF(value); | |
if (is_uninitialized == -1) { | |
goto error | |
} | |
return is_uninitialized; | |
exit: | |
PyErr_Clear(); | |
return 0; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I don't get why we need to clear the error if spec == NULL
. That sounds a bit weird as an API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code was almost a verbatim copy from _PyModuleSpec_IsInitializing
, it was not clear to me why the error was cleared given that we actually return on spec == NULL && PyErr_Occurred()
in module_getattro
. Looking at the commit that introduced it (3e429dc), it makes sense, but this is no longer needed.
We could remove this from _PyModuleSpec_IsUninitializedSubmodule
and then I could make other PR removing it from _PyModuleSpec_IsInitializing
too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to remove both here to keep then consistent
…edSubmodule Signed-off-by: Filipe Laíns <lains@riseup.net>
I have renamed |
Thanks a lot, Filipe! |
|
This is a genuine failure, we will need to revert if is not fixed in 24 h |
…ythonGH-27299) Signed-off-by: Filipe Laíns <lains@riseup.net> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
…odules (pythonGH-27299)" (pythonGH-27331) This reverts commit 8072a11.
Signed-off-by: Filipe Laíns lains@riseup.net
https://bugs.python.org/issue44717
The text was updated successfully, but these errors were encountered: