了解 Python 的 `importlib.reload`

Understanding Python's `importlib.reload`

我正在尝试了解 importlib.reload 方法的实际行为。 我举个简单的例子

import importlib
import sys
from pathlib import Path
import gc

def write_dummy_class(return_value):
    target = Path(__file__).parent / 'test_reload_import.py'
    target.write_text(
        "class Dummy:\n"
        f"    var = {return_value}\n"
        "    def run(self):\n"
        "        print(f'Dummy.run(self) >> self.var = {id(self.var):x}')\n"
        f"        return self.var\n"
    )

write_dummy_class(1)

from test_reload_import import Dummy

print(f'id Dummy: {id(Dummy):x}')
print(Dummy.run)
assert Dummy().run() == 1, "Initial one failed??"

write_dummy_class(2)

old_module = sys.modules["test_reload_import"]
old_dummy = old_module.Dummy  # Keep a reference alive
print(f'Reloading, old module: {id(old_module):x}')
new_module = importlib.reload(old_module)
print(f'Reloaded, new module: {id(new_module):x}')

print(f'id new Dummy: {id(new_module.Dummy):x}')
print(f'id old Dummy: {id(old_dummy):x}')

print(f'id Dummy: {id(new_module.Dummy):x}')
print(new_module.Dummy.run)
new_run = new_module.Dummy().run()
assert new_run == 2, f'Dummy.run() returned {new_run} instead of 2.'

这是输出:

id Dummy: 1dd320c0fa0
<function Dummy.run at 0x000001DD325CC700>
Dummy.run(self) >> self.var = 1dd31d06930
Reloading, old module: 1dd325c7950
Reloaded, new module: 1dd325c7950
id new Dummy: 1dd320c30d0
id old Dummy: 1dd320c0fa0
<function Dummy.run at 0x000001DD325CC790>
Dummy.run(self) >> self.var = 1dd31d06930
Traceback (most recent call last):
  File "test_reload.py", line 240, in <module>
    assert new_run == 2, f'Dummy.run() returned {new_run} instead of 2.'
AssertionError: Dummy.run() returned 1 instead of 2.

观察:

  1. 正在重新加载模块returns 与模块的前一个相同的内存地址。
  2. 对象确实在模块内重新加载(Dummy class 有另一个 id)。
  3. 但令我莫名其妙的是 class 变量 'Dummy.var' 的内存地址仍然指向旧地址。

有人可以向我解释最后一点吗?为什么 class 被重新加载,而 class 变量却没有?代码不是重新解释了吗?因此,var 也应该重新解释,不是吗?所以基本上得到另一个内存地址?

这引出了我的下一个问题:什么也没有重新加载?

顺便说一句,我知道小整数映射到 Python 中的相同内存地址。这不是这里的作用。当我将 class 变量从 1 更改为 2 时,它应该是另一个内存地址。或者如果它是相同的地址,它应该有不同的值。

但是在重新加载 class 之后,class 变量的内存地址并没有更新,这让我很困惑。这让我想知道还有哪些其他对象表现出相同的行为。

(Python 版本: 3.9.9)

哦,一件非常奇怪的事情是,当 运行 在 PyCharm 的“调试”下时,这个脚本工作得很好。但是当“运行”...它在第二个断言处中断。

非常感谢!

这是一个导入系统错误,Python issue 31772。如果源文件在不改变文件长度的情况下快速更新,导入系统将不会意识到它已被更改。

您的 importlib.reload 调用正在重新执行陈旧的字节码,而不是重新读取文件。这就是 var 未更新为新值的原因 - 导入系统正在为文件的 var = 1 版本使用旧字节码。