了解 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.
观察:
- 正在重新加载模块returns 与模块的前一个相同的内存地址。
- 对象确实在模块内重新加载(
Dummy
class 有另一个 id)。
- 但令我莫名其妙的是 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
版本使用旧字节码。
我正在尝试了解 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.
观察:
- 正在重新加载模块returns 与模块的前一个相同的内存地址。
- 对象确实在模块内重新加载(
Dummy
class 有另一个 id)。 - 但令我莫名其妙的是 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
版本使用旧字节码。