提问人:AmagicalFishy 提问时间:10/27/2023 最后编辑:AmagicalFishy 更新时间:11/10/2023 访问量:116
为什么 unittest 的 mock.patch.start' 会重新运行启动补丁程序的函数?
Why does unittest's `mock.patch.start` re-run the function in which the patcher is started?
问:
假设我们有两个文件:
to_patch.py
from unittest.mock import patch
def patch_a_function():
print("Patching!")
patcher = patch("to_be_patched.function")
patcher.start()
print("Done patching!")
to_be_patched.py
from to_patch import patch_a_function
def function():
pass
patch_a_function()
function()
我们运行.这将输出:python -m to_be_patched
Patching!
Patching!
- 为什么从来没有打印过?
Done patching!
- 为什么打印两次?
Patching!
我已将答案缩小到(2)向下;调用似乎再次触发。我怀疑这是因为它被导入了,但不确定为什么函数本身会第二次运行。同样,我不确定为什么在对 的任何一个调用中都没有到达该行。 不能阻塞,因为程序退出得很好,而不是挂在那里......右?patch.start
patch_a_function
to_be_patched.py
Done patching!
patch_a_function
patcher.start()
编辑:哼。看起来没有人可以复制不被打印(老实说,这是主要困难)——所以我想这只是我这边的问题Done patching!
答:
这里出现的问题与 无关,而是由于循环导入和从脚本的顶层调用造成的。patcher.start()
patch_a_function
当您运行时,它会调用 .to_be_patched
patch_a_function
现在需要导入,当它被调用时,它会再次调用。patch_a_function
patcher = patch("to_be_patched.function")
to_be_patched
patch_a_function
这次调用时,它不会重新导入,因为它已经导入,因此从这一点开始,脚本会按预期继续。patch("to_be_patched.function")
to_be_patched
您可以将步骤描述为:
1. CALL patch_a_function
2. print("Patching!")
3. CALL `patch("to_be_patched.function")`
4. `patch` CALLS __import__ of `to_be_patched`
5. `to_be_patched` is not imported yet so import module
6. During the import CALL patch_a_function
7. print("Patching!")
8. CALL `patch("to_be_patched.function")`
9. `patch` CALLS __import__ of `to_be_patched`
10. `to_be_patched` is already imported
11. Finish the rest of second time called
12. CALL patcher.start()
13. print("Done patching!")
14. CALL function() (still it is done since we import the module)
15. CALL patcher.start()
16. print("Done patching!")
17. CALL function()
如果要解决在语句中调用函数的问题:to_be_patched
if __name__ == '__main__'
from to_patch import patch_a_function
def function():
pass
if __name__ == '__main__':
patch_a_function()
function()
下面是有关顶级代码环境的详细信息:https://docs.python.org/3/library/__main__.html
评论
Patching!
loop
patch
to_be_patched
patch_a_function
patch
to_be_patched
loop
Done patching!
- 为什么从来没有打印过?
Done patching!
无法复制。
$ python -m to_be_patched
Patching!
Patching!
Done patching!
Done patching!
- 为什么打印两次?
Patching!
您的模块被导入两次。如果添加到文件中,则会很清楚:print(__name__)
to_be_patched.py
from to_patch import patch_a_function
print(f"{__name__=}")
def function():
pass
patch_a_function()
function() # note: this line doesn't actually do anything, and could be commented out
结果:
$ python -m to_be_patched
__name__='__main__'
Patching!
__name__='to_be_patched'
Patching!
Done patching!
Done patching!
当您使用时,您的模块将被加载为顶级代码,即该模块将是 .python -m to_be_patched
to_be_patched
__name__
"__main__"
使用时,mock 将首先导入补丁目标。当将补丁目标作为字符串时,如 mock 将使用 ,via pkgutil.resolve_name
,以查找要修补的正确命名空间。此方法使用 as 加载目标模块,它不是顶级代码环境。尽管加载的是相同的底层 .py 文件,但由于名称不匹配,缓存未命中: 。mock.patch
"to_be_patched.function"
importlib
__name__
"to_be_patched"
sys.modules
"__main__" != "to_be_patched"
该函数现在具有双重身份,并且存在于模块和模块中,因此您看到的是每个被调用的函数。第一个调用触发第二个调用,通过所述的双重导入机制。patch_a_function
__main__
to_be_patched
评论
to_be_patched
to_patch
3.10.0