runpy中的闭包是如何工作的?
当我尝试运行一个通过 runpy 模块加载的文件中的方法时,出现了意想不到的情况。这些方法无法访问在它们外部定义的任何变量(包括导入的模块)。我这样做的:
#test.py
import runpy
env = runpy.run_path('test', {'y':'world'})
env['fn']()
~
#test
import re
print(re.compile(r'^hello', re.IGNORECASE).sub('', "hello world"))
x = "hello"
print(x)
print(y)
def fn():
try:
print(re.compile(r'^hello', re.IGNORECASE).sub('', "hello world"))
except:
print("No re")
try:
print(x)
except:
print("No x")
try:
print(y)
except:
print("No y")
我希望 test.py 的输出是:
world
hello
world
world
hello
world
因为 fn 应该能访问 re、x 和 y。
然而,我得到的却是:
world
hello
world
No re
None
None
看起来 re 在 fn 内部没有定义,尽管正常情况下它应该是可以访问的。x 和 y 更奇怪,因为它们似乎被定义了,但值却是 None。
这是为什么呢?闭包在 runpy 中是怎么工作的?我该如何实现正常的行为,让 fn 能够“看到”外部的变量呢?
1 个回答
4
好的,这里有个关于Python如何处理模块的小知识,我知道一些,但还不完全理解。我是在使用IPython时发现这个问题的,具体可以在一个评论中看到解释。
当Python运行一个模块时,它会生成一个模块对象,这个对象的属性就是模块里的全局变量。当模块不再被使用并准备被销毁时,这些属性会被设置为None
。这时,在函数中定义的代码就会把这些当作全局变量来使用,就像你发现的那样。你可以通过在你的文件中添加def g(): return globals()
来演示这个现象,然后调用env["g"]()
。
我不确定用runpy
是否有办法解决这个问题。IPython使用了一些复杂的代码来重用一个模块对象,以便运行其他文件,并缓存它的__dict__
的副本,以保持其中的引用有效。如果你感兴趣,可以看看magic_run
函数。