【问题标题】:Reload function fails to erase removed variables重新加载功能无法擦除已删除的变量
【发布时间】:2020-03-15 17:27:57
【问题描述】:

我正在尝试访问正在修改的模块 中的变量,而主脚本 使用reload 运行。但是 reload 函数无法擦除已从 module 中删除的变量。如何强制 python 删除它们?

这是我的代码

我的 module.py

a = 1
b = 2

我的主要scipt.py

import time
from importlib import reload

import module

while True:
    reload(module)
    print('------- module reloaded')
    try:
        print('a: ', module.a)
    except AttributeError:
        print('a: ', 'undefined')
    try:
        print('b: ', module.b)
    except AttributeError:
        print('b: ', 'undefined')
    try:
        print('c: ', module.c)
    except AttributeError:
        print('c: ', 'undefined')

    time.sleep(5)

正如预期的那样,如果我使用 python (3.5.1) 运行我的 脚本,我会得到输出:

------- module reloaded
a:  1
b:  2
c:  undefined

但是当我将 module.py 更改如下时,我得到了一个意外的行为:

# a = 1
b = 3
c = 4

我有以下输出:

------- module reloaded
a:  1
b:  3
c:  4

这意味着reload 正确更新了b 的值并添加了新变量c。但它未能擦除已从 模块 中删除的变量 a。它似乎只对 module 新版本中的变量执行更新。如何强制 reload 函数删除删除的值?

感谢您的帮助

【问题讨论】:

  • 如果你将reload()的返回值赋值给某个东西,然后对返回值做同样的检查,会发生什么?

标签: python python-3.x python-import python-importlib


【解决方案1】:

这里的问题是reload 是用execs 现有缓存模块命名空间中模块代码的当前版本的代码实现的。这样做的原因是reload 旨在产生全局影响; reload(module) 不仅为 重新加载它,它还会更改每个其他模块的个人导入的module 副本。如果它创建了一个 new 模块命名空间,其他模块仍然会缓存旧模块;虽然在execing 之前擦除旧模块的内容可能会有所帮助,但它可能会破坏已经导入的包子模块,触发竞争条件(线程可能会在reload 之前和之后看到module.a,但它会在reload期间神秘消失了片刻)等

作为the docs note:

当一个模块被重新加载时,它的字典(包含模块的全局变量)被保留。重新定义名称将覆盖旧定义,因此这通常不是问题。 如果模块的新版本没有定义旧版本定义的名称,则旧定义仍然存在。

如果您绝对必须这样做,有一些解决方法可以绕过此安全机制。最简单的方法是从模块缓存中删除模块并重新导入,而不是reloading 它:

import sys  # At top of file

del sys.modules['module']
import module

这不会更新模块的任何其他导入器(它们会保留陈旧的缓存),但如果该模块仅在您的模块中使用,那将起作用。

可能工作的另一种方法(未经测试,有点疯狂)是在reloading 之前从模块中显式删除所有公共名称,例如:

# Intentionally done as list comprehension so no modification to module's globals dict
# occurs while we're iterating it
# Might make sense to use dir or iterate module.__all__ if available instead of using vars;
# depends on design
for name in [n for n in vars(module) if not n.startswith('_')]:
    try:
        delattr(module, name)
    except Exception:
        pass  # Undeletable attribute for whatever reason, just ignore and let reload deal with it
reload(module)  # Now exec occurs in clean namespace

这将避免陈旧的缓存问题,以换取精神错乱。

真的,答案是“不要使用依赖于生产的设计reloading”;如果模块只是数据,则将其存储为 JSON 文件或类似文件,然后重新解析它(这通常比 Python 导入机制导入模块所花费的成本很多)。

【讨论】:

  • module.__dict__.clear() 这样的东西怎么样?
  • @MadPhysicist:这可能行得通(vars(module).clear() 是我希望避免直接访问 dunders,但效果相同)。我在很大程度上避免了它,因为 reload 机制可能需要一些对加载器本身很重要的模块属性(__spec____loader____package__ 是一些,我不想拖网文档以确保我不会错过任何其他人),并且将它们吹走可能会破坏 reload 本身。
  • 为了给你一些上下文,我的应用程序允许用户修改部分代码。然后,我首先设计了主流程,使其重新加载修改后的模块(不仅包含变量,还包含函数、类等),而不是加载 JSON 文件。谢谢你的明确回答,它将帮助我重新设计整个事情。
  • @DareYang:您可能想对此进行更多解释。如果应用程序在任何半特权上下文中运行(例如在 Web 服务器上),允许用户执行代码就是安全问题的定义;即使它在本地运行,在他们自己的凭据下,也很难使其稳定(一个用户输入错误,整个事情都崩溃了)。
【解决方案2】:

这是设计使然。来自docs

当一个模块被重新加载时,它的字典(包含模块的全局变量)被保留。重新定义名称将覆盖旧定义,因此这通常不是问题。 如果模块的新版本没有定义旧版本定义的名称,则旧定义仍然存在。

一般来说,reload 是为了方便交互式口译会话;它并不是真的要在实际脚本中使用。您可能应该重新考虑您的设计。 (例如,module 是否有理由不能成为常规文本文件?)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-29
    • 1970-01-01
    • 1970-01-01
    • 2012-06-19
    相关资源
    最近更新 更多