gpt4 book ai didi

python:函数*有时*维护对其模块的引用

转载 作者:行者123 更新时间:2023-12-01 05:17:32 25 4
gpt4 key购买 nike

如果我 execfile 一个模块,并删除对该模块的所有(我的)引用,它的功能将继续按预期工作。这很正常。

但是,如果该 execfile 模块导入其他模块,并且我删除了对这些模块的所有引用,则这些模块中定义的函数开始将其所有全局值视为 None。当然,这会导致事情以一种非常令人惊讶的方式彻底失败(例如,字符串常量上的 TypeError NoneType)。

令我惊讶的是,解释器在这里做了一个特例; execfile 似乎不够特别,不会导致函数在模块引用时表现出不同的行为。

我的问题:对于 execfile 模块导入的模块,是否有任何干净的方法可以使 execfile 函数行为递归(或在有限上下文中全局)?

<小时/>

对于好奇的人:

该应用程序是在 buildbot 下可靠的配置重新加载。无论好坏,buildbot 配置都是可执行的 python。如果可执行配置是单个文件,则一切工作得相当顺利。如果该配置被拆分为模块,则由于 __import__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()

最佳答案

我认为您不需要这里的“acceptance_test”部分。问题实际上不是弱引用,而是模块在破坏时的行为。他们在删除时清除__dict__。我依稀记得这样做是为了打破引用循环。我怀疑函数闭包中的全局引用做了一些奇特的事情来避免每次调用时进行哈希查找,这就是为什么你得到 None 而不是 NameError

这是一个更短的 sscce:

import gc
import sys
import contextlib
from textwrap import dedent


@contextlib.contextmanager
def held_modules():
modules_before = sys.modules.keys()
yield
for module in sys.modules.keys():
if module not in modules_before:
del sys.modules[module]
gc.collect() # force collection after removing refs, for demo purposes.

def main():
open('config_module.py', 'w').write(dedent('''
GLOBAL = 'GLOBAL'
def config():
print 'config! (old implementation)'
print GLOBAL
'''))
open('main.py', 'w').write(dedent('''
from config_module import *
'''))

with held_modules():
namespace = {}
exec open('main.py') in namespace
config = namespace['config']
config()

if __name__ == '__main__':
main()

或者,换句话说,不要删除模块并期望其内容继续运行。

关于python:函数*有时*维护对其模块的引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22943544/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com