为什么模拟补丁不能按预期工作?

Why mock patch not works as expected?

b.py:

from unittest.mock import patch

def hello():
    print("hello")
    return 1

@patch("b.hello", return_value="wow")
def fun(mock_hello):
    print(hello())

print("start")
fun()
print("end")

我用python3:

pie@pie:~$ python3 --version
Python 3.6.9

对于上面的代码,我希望得到下一个,因为我已经嘲笑了 hello:

start
wow
end

但实际上,我得到了下一个:

pie@pie:~$ python3 b.py
start
start
wow
end
hello
1
end

我完全被模拟行为弄糊涂了,发生了什么事?

如果有人感兴趣,请更新答案,根本原因是同一文件中的 patch targetpatch。不在同一个文件里就不会有问题。

unittest/mock.py中的patch function的源代码中,我们可以看到实际上它使用__import__来导入目标:

patch -> getter, attribute = _get_target(target) -> getter = lambda: _importer(target) -> thing = __import__(import_path)

所以上面有问题的代码的行为如下:

  1. 作为顶级脚本 b.py 启动,它首先打印 start
  2. 作为顶级脚本,它调用fun(),此时,patch将使用__import__导入b.hello。所以 b.py 现在作为一个 python 模块再次执行。作为 python 模块,它打印 start.
  3. 作为 python 模块,它调用 fun,因为模块已经导入,所以这不会启动另一个导入。作为一个模块 运行,hello() 实际上意味着 b.hello(),已经打补丁了,它应该 returns wow。所以,wow 现在被打印出来了。
  4. 作为python模块,它最终打印出end.
  5. 现在 python 模块执行完成,b.py 作为顶级脚本继续 运行 hello(),但现在 patch 确实已修补 b.hello,而不是 hello,所以它仍然打印 hello & 1.
  6. 最后,作为 python 顶级脚本它打印 end