如果我执行一个模块,并删除所有(我的)对该模块的引用,它的功能将继续按预期工作。这很正常。
但是,如果该 execfile'd 模块导入其他模块,并且我删除了对这些模块的所有引用,则这些模块中定义的函数开始将它们的所有全局值视为无。当然,这会导致事情以一种非常令人惊讶的方式失败(例如,字符串常量上的 TypeError NoneType)。
我很惊讶口译员在这里做了一个特殊情况;execfile
似乎没有足够的特殊性来导致函数在模块引用中表现不同。
我的问题:对于由 execfile'd 模块导入的模块,是否有任何干净的方法可以使 execfile 函数行为递归(或在有限上下文中全局)?
给好奇的人:
该应用程序是在 buildbot 下可靠的配置重新加载。buildbot 配置是可执行的 python,无论好坏。如果可执行配置是单个文件,则一切正常。__import__
如果将该配置拆分为模块,则由于and的语义,来自顶级文件的任何导入都会卡在原始版本中sys.modules
。我的策略是在配置前后保持 sys.modules 的内容不变,这样每次重新配置看起来就像一个初始配置。除了上述函数全局参考问题外,这几乎可以工作。
这是该问题的可重复演示:
import gc
import sys
from textwrap import dedent
class DisableModuleCache(object):
"""Defines a context in which the contents of sys.modules is held constant.
i.e. Any new entries in the module cache (sys.modules) are cleared when exiting this context.
"""
modules_before = None
def __enter__(self):
self.modules_before = sys.modules.keys()
def __exit__(self, *args):
for module in sys.modules.keys():
if module not in self.modules_before:
del sys.modules[module]
gc.collect() # force collection after removing refs, for demo purposes.
def reload_config(filename):
"""Reload configuration from a file"""
with DisableModuleCache():
namespace = {}
exec open(filename) in namespace
config = namespace['config']
del namespace
config()
def main():
open('config_module.py', 'w').write(dedent('''
GLOBAL = 'GLOBAL'
def config():
print 'config! (old implementation)'
print GLOBAL
'''))
# if I exec that file itself, its functions maintain a reference to its modules,
# keeping GLOBAL's refcount above zero
reload_config('config_module.py')
## output:
#config! (old implementation)
#GLOBAL
# If that file is once-removed from the exec, the functions no longer maintain a reference to their module.
# The GLOBAL's refcount goes to zero, and we get a None value (feels like weakref behavior?).
open('main.py', 'w').write(dedent('''
from config_module import *
'''))
reload_config('main.py')
## output:
#config! (old implementation)
#None
## *desired* output:
#config! (old implementation)
#GLOBAL
acceptance_test()
def acceptance_test():
# Have to wait at least one second between edits (on ext3),
# or else we import the old version from the .pyc file.
from time import sleep
sleep(1)
open('config_module.py', 'w').write(dedent('''
GLOBAL2 = 'GLOBAL2'
def config():
print 'config2! (new implementation)'
print GLOBAL2
## There should be no such thing as GLOBAL. Naive reload() gets this wrong.
try:
print GLOBAL
except NameError:
print 'got the expected NameError :)'
else:
raise AssertionError('expected a NameError!')
'''))
reload_config('main.py')
## output:
#config2! (new implementation)
#None
#got the expected NameError :)
## *desired* output:
#config2! (new implementation)
#GLOBAL2
#got the expected NameError :)
if __name__ == '__main__':
main()