Skip to content
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-33237: Improve AttributeError message for partially initialized module. #6398

Merged
merged 11 commits into from
Oct 30, 2018

Conversation

serhiy-storchaka
Copy link
Member

@serhiy-storchaka serhiy-storchaka commented Apr 6, 2018

Copy link
Member

@brettcannon brettcannon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor thing to fix, otherwise LGTM!

initializing = PyObject_IsTrue(value);
Py_DECREF(value);
if (initializing < 0)
PyErr_Clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curly braces around the statement.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if test value == Py_True instead of PyObject_IsTrue(value)? This would simplify the code, and I don't think that we should expect any true value except True.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm torn. I do agree the code is simpler and you shouldn't expect it, but I also don't know if it's worth restricting the expectations that tightly. Then again, who is going to set this other than import itself?

IOW I don't have an opinion so whatever you prefer. 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And please note, that this PR changes the behavior of PyImport_ImportModuleLevelObject(). I have extracted the common code into _PyModule_IsInitializing(), but the code in PyImport_ImportModuleLevelObject() is not exactly the same as in module_getattro(). It used _PyObject_GetAttrId(mod, &PyId___spec__), but now it uses _PyDict_GetItemId(((PyModuleObject *)m)->md_dict, &PyId___spec__), and supports only the module type instances. I.e. __getattr__() will be now bypassed when looking up __spec__.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a breaking changing due to the fact that people could return any object for their module thanks to importlib.abc.Loader.create_module().

@vstinner
Copy link
Member

How can I trigger the modified code? The following circular import error doesn't seem to be covered by this change:

vstinner@apu$ cat a.py 
from b import B
class A: pass

vstinner@apu$ cat b.py 
from a import A
class B: pass

vstinner@apu$ ./python -c 'import a'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/vstinner/prog/python/master/a.py", line 1, in <module>
    from b import B
  File "/home/vstinner/prog/python/master/b.py", line 1, in <module>
    from a import A
ImportError: cannot import name 'A' from 'a' (/home/vstinner/prog/python/master/a.py)

I get the same traceback and exception with this change.

@serhiy-storchaka
Copy link
Member Author

See an example on the tracker.

@serhiy-storchaka
Copy link
Member Author

Restored support of non-module subclasses and added tests. @brettcannon, please make review again.

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I really like the much better error message!

I just a few minor remarks on the implementation and the NEWS entry.

}
}
}
PyErr_Clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you always clear the current exception? What if the function is called with an exception set? Maybe start with a "assert(!PyErr_Occurred());".

I prefer the old code which only clears the exception on error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not wrong to call this function with NULL and an exception set. Checking this here saves several lines of code and/or indentation level at caller places.

The exception is cleared on error in most cases. The only exception from this rule is for _PyDict_GetItemId() returned NULL.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, it makes sense.

int
_PyModuleSpec_IsInitializing(PyObject *spec)
{
if (spec != NULL && spec != Py_None) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just return 0 (without calling PyErr_Clear) if this condition is false? It would avoid one identation level :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to call PyErr_Clear() in all cases except spec == Py_None. The latter condition was added for mistake (left from old versions). I'll remove it.

@vstinner
Copy link
Member

See an example on the tracker.

Ah thanks, I succeded to reproduce the error message and I like it :-)

@asottile
Copy link
Contributor

@vstinner I made a patch to improve the circular-from import as well: #15308

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-feature A feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants