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
runpy.run_path doesn't set __package__ correctly #59435
Comments
(Python 3.2.3)
/tmp/a.py -------------------------------------- FOO = 'bar'
def f():
print(FOO)
f() /tmp/b.py -------------------------------------- # Hack needed for:
# python3 /tmp/b.py,
# python3 -m /tmp/b
# runpy.run_path('/tmp/b.py')
from os.path import dirname
__path__ = [dirname(__file__)]
del dirname
# Hack needed for:
# python3 -m /tmp/b
if __name__ == '__main__' and not __package__:
__package__ = '__main__'
from . import a
def g():
print(a.FOO)
g() ~$ python3
>>> import runpy
>>> d = runpy.run_module('/tmp/a')
bar
>>> d2 = runpy.run_path('/tmp/a.py')
bar
>>> d['f']
<function f at 0xb7451b6c>
>>> d['FOO']
'bar'
>>> d['f']()
bar
>>> d2['f']
<function f at 0xb7451bac>
>>> d2['FOO']
'bar'
>>> d2['f']()
None
>>> d3 = runpy.run_path('/tmp/b.py')
bar
bar
>>> d3['g']
<function g at 0xb746e26c>
>>> d3['a']
<module '<run_path>.a' from '/tmp/a.py'>
>>> d3['g']()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/tmp/b.py", line 15, in g
print(a.FOO)
AttributeError: 'NoneType' object has no attribute 'FOO' Notice that run_module gets this right, as d'f' prints 'bar' but d2'f' and d3'g' do not.
Continuing from #1 without having closed the interpreter:
>>> d4 = runpy.run_path('/tmp/b.py')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.2/runpy.py", line 250, in run_path
return _run_module_code(code, init_globals, run_name, path_name)
File "/usr/lib/python3.2/runpy.py", line 83, in _run_module_code
mod_name, mod_fname, mod_loader, pkg_name)
File "/usr/lib/python3.2/runpy.py", line 73, in _run_code
exec(code, run_globals)
File "/tmp/b.py", line 12, in <module>
from . import a
ImportError: cannot import name a
>>> '<run_path>' in sys.modules
False
>>> '<run_path>.a' in sys.modules
True
>>> d3['a'].f()
bar
>>> del sys.modules['<run_path>.a']
>>> d4 = runpy.run_path('/tmp/b.py')
bar
bar
>>>
run_module, on the other hand, also alters sys.modules, but this does not prevent the module from being run, only from the secondary module from being re-imported:
[Create an empty file /tmp/__init__.py]
>>> sys.path = ['/'] + sys.path
>>> d5 = runpy.run_module('tmp.b')
bar
bar
>>> d6 = runpy.run_module('tmp.b')
bar
>>> d7 = runpy.run_module('tmp.b')
bar
>>> 'tmp' in sys.modules
True
>>> 'tmp.b' in sys.modules
False
>>> 'tmp.a' in sys.modules
True [This was the only way I could get run_module to run /tmp/b.py, regardless of the presence or lack of the path and __package__ hacks at the top of the file, or any other changes I've experimented with. runpy.run_module('/tmp/b'), runpy.run_module('b') [with '/tmp' in sys.path] would generally result in: python3 /tmp/b.py and python3 -m /tmp/b run fine.]
|
“python3 -m /tmp/b” is invalid IIUC. -m takes a module name, not a path. |
Firstly, I think you've identified a real bug with __package__ not being set correctly when using runpy.run_path (and I have updated this issue title accordingly). I have also created a separate bug report (bpo-15272) for the bizarre behaviour you identified in runpy.run_module - names containing "/" characters should be rejected as invalid. One of the main reasons you're having trouble though is that you can only do relative imports when inside a package - at the top level (as both of your modules are) relative imports are illegal. By forcing package to "__main__" you are claiming a location in the package namespace of "__main__.__main__" which doesn't make any sense. The module life cycle problem for functions is covered in bpo-812369. The only reason you're not hitting it in the run_module case is that when "alter_sys" is False, no temporary module is created. For data attributes (the intended use case for runpy), this all works fine regardless, but functions (which retain a reference to the original module namespace) will only work properly with alter_sys turned off. There should probably be a general disclaimer in the module docs that functions and classes are not guaranteed to be valid after using runpy, and importlib.import_module should be used instead for such cases. bpo-9235 looks into ways the runpy module might be enhanced with a CodeRunner class for other reasons, but the same mechanism could be used to keep the temporary module alive without storing it in sys.modules. |
You may also have identified a bug with pkgutil's import emulation failing to clean up sys.modules correctly when an import fails. |
Sorry, that's not accurate - you have enough code in b.py to make the relative import work by convincing the interpreter it's actually being done in a package. So what you're seeing in that regard is the fact that runpy is not any kind of sandbox - it shares process global state, including the import system, with all other modules. While the temporary module will be reverted automatically by runpy, any child imports will always remain visible in sys.modules, and any other side effects will remain in place (e.g. codec registrations). |
New changeset 3b05cf877124 by Nick Coghlan in branch '3.2': New changeset 8a44e7c0fa30 by Nick Coghlan in branch 'default': |
New changeset 4880aac5c665 by Nick Coghlan in branch '3.2': New changeset 416cd57d38cf by Nick Coghlan in branch 'default': |
This broke some 3.2 buildbots, e.g.: |
*grumble*grumble*os-x-and-its-crazy-symlink-as-tmp-dir*grumble*grumble* I'm fairly sure I've tripped over this kind of thing before, so I believe I know how to fix it (add a realpath() call when figuring out the expected path value). The buildbots will let me know if I'm right, of course :) |
New changeset 07ed744a47f6 by Nick Coghlan in branch '3.2': New changeset cb7e8ee489a1 by Nick Coghlan in branch 'default': |
Closing again, since the OS X buildbot was happy with the change on 3.2. If trunk fails (which it shouldn't), that should be filed as a new issue (as it would almost certainly relate to the move from the pkgutil emulation to importlib). |
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: