为什么补丁库的行为会根据值的导入方式而改变?

Why does the behavior of the patch library change depending on how values are imported?

mock 库中的 patch 函数对导入内容的方式很敏感。为什么我不能只使用函数最初定义的完全限定名称而不管它是如何导入其他模块的,这有什么深刻的原因吗?

使用 "module import" 效果很好

patch_example.py:

# WORKS!
from mock import patch
import inner

def outer(x):
    return ("outer", inner.inner(x))

@patch("inner.inner")
def test(mock_inner):
    mock_inner.return_value = "MOCK"
    assert outer(1) == ("outer", "MOCK")
    return "SUCCESS"

if __name__ == "__main__":
    print test()

inner.py:

def inner(x):
    return ("inner.inner", x)

运行 python patch_example.py 输出成功。

但是,更改导入可能会产生相当大的后果

使用模块别名仍然有效

# WORKS!
from mock import patch
import inner as inner2

def outer(x):
    return ("outer", inner2.inner(x))

@patch("inner.inner")
def test(mock_inner):
    mock_inner.return_value = "MOCK"
    assert outer(1) == ("outer", "MOCK")
    return "SUCCESS"

if __name__ == "__main__":
    print test()

但是,直接导入符号需要您更改完全限定名称。

直接导入,inner.inner 作为完全限定名称。

# FAILS!
from mock import patch
from inner import inner

def outer(x):
    return ("outer", inner(x))

@patch("inner.inner")
def test(mock_inner):
    mock_inner.return_value = "MOCK"
    assert outer(1) == ("outer", "MOCK")
    return "SUCCESS"

if __name__ == "__main__":
    print test()

生产

% python patch_example.py
Traceback (most recent call last):
  File "patch_example.py", line 14, in <module>
    print test()
  File "/usr/local/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "patch_example.py", line 10, in test
    assert outer(1) == ("outer", "MOCK")
AssertionError

如果我将完全限定路径更新为 patch_example.inner,补丁仍然失败。

# FAILS!
from mock import patch
from inner import inner

def outer(x):
    return ("outer", inner(x))

@patch("patch_example.inner")
def test(mock_inner):
    mock_inner.return_value = "MOCK"
    assert outer(1) == ("outer", "MOCK")
    return "SUCCESS"

if __name__ == "__main__":
    print test()

% python patch_example.py

Traceback (most recent call last):
  File "patch_example.py", line 14, in <module>
    print test()
  File "/usr/local/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "patch_example.py", line 10, in test
    assert outer(1) == ("outer", "MOCK")
AssertionError

使用 __main__.inner 作为我的完全限定名称修补正确的东西。

# WORKS!
from mock import patch
from inner import inner

def outer(x):
    return ("outer", inner(x))

@patch("__main__.inner")
def test(mock_inner):
    mock_inner.return_value = "MOCK"
    assert outer(1) == ("outer", "MOCK")
    return "SUCCESS"

if __name__ == "__main__":
    print test()

打印"SUCCESS"

那么,当 inner 被导入为 from inner import inner 时,为什么我不能使用原始符号的完全限定名称 inner.inner 或使用主 python 模块而不是 __name__?

在 OS X 上使用 Python 2.7.12 进行测试。

问题是,一旦您直接导入符号,在您在 __main__ 模块中使用的绑定与在 inner 模块中找到绑定。所以 patching 模块 不会 改变已经导入的符号。

使用别名导入模块无关紧要,因为 patch 将查找 sys.modules 字典,它仍然跟踪原始名称,所以这就是为什么它有效(实际上:调用时mock 模块是新导入的,因此在调用 patch)

时导入它的名称无关紧要

换句话说:您必须修补 both 绑定,因为它们实际上是不相关的。 patch 无法知道对 inner.inner 的所有引用在哪里结束并修补它们。

在这种情况下,patch 的第二个参数可能有助于指定一个现有的模拟对象,该模拟对象可以共享以修补所有绑定。