-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
Traceback wrong on ImportError while executing a package #58493
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
Comments
It is very simple to reproduce this error. package/ The __init__ imports a missing module: import missing_module And the __main__ imports from it: from . import missing_module Now I get the following output which is not what I am expecting:
|
I've no idea what having a file called __main__.py is likely to do so can someone comment please. |
A file called “package/main.py” is executed as a script by “python -m package”. See <https://docs.python.org/dev/library/__main__.html\>. I’ve came across this issue myself. You don’t even need the __main__.py file to be doing anything special, as long as the __init__.py raises an ImportError, I think. On Python 3.4 the report is even more convoluted: /sbin/python3: Error while finding spec for 'package.__main__' (<class 'ImportError'>: No module named 'missing_module'); 'package' is a package and cannot be directly executed I dunno what “finding spec” means, and packages _can_ be directly executed if they have a __main__ module, so at least the last bit is definitely wrong. |
The message also needs to include the file and line number of the ImportError. |
The relevant code is in the _get_module_details() function at Lib/runpy.py:101. There are a few of things going on:
I’m not sure what the best way to fix this is. Perhaps add an internal ImportError subclass (or subclasses) that is only used for the “No module named . . .” errors, or only used for errors appropriate for final handler in step 3. |
Closely related: bpo-19771, which seems to be complaining about a the error message when __main__.py generates an ImportError. |
The patches at bpo-19771 should remove the part of the message that incorrectly says “. . . is a package and cannot be directly executed”. However that still leaves the problem of the suppressed traceback. I am posting runpy-traceback.patch here which adds some tests to check if the traceback is suppressed. The offending test is marked @expectedfailure. It also points out where the exception is being incorrectly caught, but I haven’t thought of a nice way to fix the problem. Other changes in the runpy-traceback.patch:
|
Posting finding-spec.patch which just has another test I wrote. It tests if AttributeError/ValueError/TypeError gets wrapped in the “finding spec” ImportError, though I’m not sure if this is a bug or a feature, hence I kept it separate. And again I’m not sure of a good way to fix it either. |
I suspect a proper solution of this bug would also help with bpo-16217 (provide only the relevant traceback for exceptions from user code). |
Now I have a deeper understanding I think this can be handled separately to bpo-16217. This patch pulls together my previous two patches, and adds a fix. There are two aspects of my fix:
I think my patch should avoid the main problem in bpo-19771 as well. Please review my patch. There are so many error possibilities; it is hard to be sure I have got them all right. |
Martin, your patch looks good to me, and is at the very least an improvement over the status quo (which clearly traps exceptions that it shouldn't), so I'd say go ahead and apply it. Thanks for digging into this and figuring out a clean solution. |
Thanks for the review Nick. You removed Python 3.4 from the versions; do you think it is not worth the risk committing in 3.4? I understand the deadline for the final release of 3.4 is the end of this week. |
Right, while I agree this is a bug fix that makes sense to apply to 2.7 and 3.5, it's also a large enough change to runpy's control flow logic that I'm wary of including it in the final 3.4 binary release. |
New changeset 784a64a21fd0 by Martin Panter in branch '3.5': New changeset 01397c11ebb8 by Martin Panter in branch 'default': |
New changeset c4e950338e79 by Martin Panter in branch '2.7': |
Even with my committed fix, tracebacks triggered by initializing a parent package are still suppressed: $ ./python -m hello
Traceback (most recent call last):
File "/media/disk/home/proj/python/cpython/Lib/runpy.py", line 158, in _run_module_as_main
mod_name, mod_spec, code = _get_module_details(mod_name, _Error)
File "/media/disk/home/proj/python/cpython/Lib/runpy.py", line 116, in _get_module_details
__import__(mod_name) # Do not catch exceptions initializing package
File "/media/disk/home/proj/python/cpython/hello/__init__.py", line 1, in <module>
import random; random.dsgjdgj
AttributeError: module 'random' has no attribute 'dsgjdgj'
[Exit 1]
$ ./python -m hello.submodule
/media/disk/home/proj/python/cpython/python: Error while finding spec for 'hello.submodule' (<class 'AttributeError'>: module 'random' has no attribute 'dsgjdgj')
[Exit 1] Fixing this in general might require a loop around the find_spec() call as in init-ancestor.patch. But I’m unsure if it is worth the extra complication. |
I'm wondering if there might be a simpler option: use rpartition() to strip off any trailing segment (whether that's "__main__" or not), and then always do a plain dynamic import of that package (if any). Something like the following at the start of _get_module_details():
The key is that we *don't* want to be relying on the fact find_spec() will import parent packages implicitly. |
I think the problem with doing a blind import of the parent package is it would be hard to differentiate between the ImportError when the package (or an ancestor) is missing, versus a user-generated ImportError. Maybe you could inspect the exception as a workaround (untested): try: |
That import will only import the parent package (if there is one), not the module itself. Imports from __main__ will still happen during the exec call later on. |
Yes, the package is all we need to import. I understand this bug is all about importing the parent package, and not __main__. If you read the original report, it is __init__.py that is trying to import a missing module. |
init-ancestor.2.patch implements the single __import__ with ImportError.name checking |
Ah, I think I understand the problem you're getting at now:
The exception handler in the new code snippet addresses that by checking the name attribute on the ImportError, but could likely use a comment explaining the subtlety. The way you've handled checking the result of the rpartition call also highlights a deficiency in the runpy docs, and likely also the test suite as well: runpy.run_module and the -m switch only work with absolute module names, but this isn't stated explicitly in the documentation, nor is there any early check in _get_module_details() to bail out if given a relative module name. |
The test suite does check run_module() with some relative names; see test_invalid_names() in test_runpy. But there does not appear to be a test for “python -m .relative”. Changes in init-ancestor.3.patch:
|
+1, latest iteration looks good to me. |
New changeset 3202d143a194 by Martin Panter in branch '3.5': New changeset a526ebcfd31d by Martin Panter in branch 'default': |
I committed a slightly modified version to 3.5+. I stopped wrapping the new ImportError, and let the existing find_spec() handler wrap it instead. That way the existing error messages stay the same. However I cannot figure out an easy way to do a similar fix for 2.7, where ImportError.name does not exist. Therefore I propose to leave 2.7 as it is, with just my first commit. This means that 2.7 still omits the traceback if a parent package fails to initialize: $ ./python -m package.submodule
[. . .]/python: No module named missing_module It also means we see an unnecessary traceback with broken *.pyc files, although the eventual error message is improved. This case was reported in <https://bugs.python.org/issue19771#msg248193\>: $ mkdir bad_pyc
$ : > bad_pyc/__init__.pyc # Create empty file
$ python2 -m bad_pyc # Original behaviour
/sbin/python2: Bad magic number in bad_pyc/__init__.pyc; 'bad_pyc' is a package and cannot be directly executed
$ ./python -m bad_pyc # Current behaviour
Traceback (most recent call last):
File "[. . .]/Lib/runpy.py", line 163, in _run_module_as_main
mod_name, _Error)
File "[. . .]/Lib/runpy.py", line 111, in _get_module_details
__import__(mod_name) # Do not catch exceptions initializing package
ImportError: Bad magic number in bad_pyc/__init__.pyc I propose to leave these two cases as they currently are in 2.7, unless someone has any ideas how to improve them. |
Leaving Python 2.7 alone makes sense to me - runpy is heavily dependent on the import system, so there are going to be limits to the improvements that can be made there. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: