Gregory Nisbet Gregory Nisbet - 3 months ago 12
Python Question

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

The

patch
function from the
mock
library is sensitive to how things are imported. Is there a deep reason why I can't just use the fully qualified name where the function was originally defined regardless of how it is imported in other modules?

using a "module import" works fine

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)


Running
python patch_example.py
just outputs success.

However, changing the import can have pretty dramatic consequences

Using a module alias still works

# 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()


directly importing the symbol, however, requires you to change the fully qualified name.

direct import,
inner.inner
as fully qualified name.

# 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()


produces

% 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


If I update the fully qualified path to
patch_example.inner
, the patch still fails.

# 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


using
__main__.inner
as my fully qualified name patches the right thing though.

# 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()


prints "SUCCESS"

So, why can't I patch the value of inner when it's imported as
from inner import inner
using either the fully qualified name of the original symbol
inner.inner
or the use the name of the main python module rather than
__name__
?

Tested with Python 2.7.12 on OS X.

Answer

The problem is that once you directly import the symbol there is no link whatsoever between the binding you are using in the __main__ module and the binding found in the inner module. So patching the module does not change the already imported symbols.

Importing a module using an alias doesn't matter because patch will lookup the sys.modules dictionary, which still keep track of the original name, so that's why this works (actually: when calling the mock the module is freshly imported, so it doesn't matter the name in which you imported it when calling patch)

In other words: you have to patch both bindings because they are effectively unrelated. There is no way for patch to know where all references to inner.inner ended up and patch them.

In this situation the second argument of patch might be useful to specify an existing mock object that can be shared to patch all bindings.

Comments