Skip to main content
 首页 » 编程设计

python之模拟上的相同调用链是否总是返回完全相同的模拟对象

2025年05月04日17www_RR

代码:

from unittest.mock import Mock 
 
mock = Mock() 
 
print('mock.f():', id(mock.f())) 
print('mock.f().g().h():', id(mock.f().g().h())) 
print('mock():', id(mock())) 
print('mock().f():', id(mock().f())) 
print() 
 
print('mock.f():', id(mock.f())) 
print('mock.f().g().h():', id(mock.f().g().h())) 
print('mock():', id(mock())) 
print('mock().f():', id(mock().f())) 
print() 
 
print('mock.f(1):', id(mock.f(1))) 
print('mock.f(2).g(3).h(4):', id(mock.f(2).g(3).h(4))) 
print('mock(5):', id(mock(5))) 
print('mock(6).f(7):', id(mock(6).f(7))) 
print() 

输出:

mock.f(): 4483288208 
mock.f().g().h(): 4483354192 
mock(): 4483368976 
mock().f(): 4483708880 
 
mock.f(): 4483288208 
mock.f().g().h(): 4483354192 
mock(): 4483368976 
mock().f(): 4483708880 
 
mock.f(1): 4483288208 
mock.f(2).g(3).h(4): 4483354192 
mock(5): 4483368976 
mock(6).f(7): 4483708880 

观察:

输出表明,无论我们进行了多少次调用,对模拟的指定链式函数调用在程序的生命周期内总是返回相同的对象。

比如第一次调用mock.f().g().h(),第二次调用mock.f().g().h( ),甚至使用不同参数的第三次调用 mock.f(2).g(3).h(4) 也返回完全相同的对象。

问题:

  • 我们可以依赖这种行为吗?是否保证在程序的生命周期内,mock.f().g().h() 会返回完全相同的模拟对象?
  • 是否可以保证即使具有不同参数的相同调用链,例如,mock.f(2).g(3).h(4) 也将返回与 a 相同的对象mock.f().g().h()?
  • 这些东西都记录在某处吗?

背景:

我问这个问题的原因是为了不写这样的代码:

from urllib import request 
from unittest.mock import Mock, patch 
 
with patch('urllib.request.urlopen') as mock_urlopen: 
    mock_urlopen.return_value = Mock() 
    mock_urlopen().getcode.return_value = 200 
    assert request.urlopen('').getcode() == 200 

我可以编写这样的代码:

from urllib import request 
from unittest.mock import Mock, patch 
 
with patch('urllib.request.urlopen') as mock_urlopen: 
    mock_urlopen().getcode.return_value = 200 
    assert request.urlopen('').getcode() == 200 

以上示例过于简单,仅供演示之用。我想保留独立的示例。但是如果我可以依赖这个特性,当函数调用链很长时,它会变得非常方便。这就是为什么我正在寻找表明我可以依赖此行为的某种引用或文档。

请您参考如下方法:

如果您查看文件 lib/python3.7/unittest/mock.py

def __getattr__(self, name): 
    if name in {'_mock_methods', '_mock_unsafe'}: 
        raise AttributeError(name) 
    elif self._mock_methods is not None: 
        if name not in self._mock_methods or name in _all_magics: 
            raise AttributeError("Mock object has no attribute %r" % name) 
    elif _is_magic(name): 
        raise AttributeError(name) 
    if not self._mock_unsafe: 
        if name.startswith(('assert', 'assret')): 
            raise AttributeError(name) 
 
    result = self._mock_children.get(name) 
    if result is _deleted: 
        raise AttributeError(name) 
    elif result is None: 
        wraps = None 
        if self._mock_wraps is not None: 
            # XXXX should we get the attribute without triggering code 
            # execution? 
            wraps = getattr(self._mock_wraps, name) 
 
        result = self._get_child_mock( 
            parent=self, name=name, wraps=wraps, _new_name=name, 
            _new_parent=self 
        ) 
        self._mock_children[name]  = result 
 
    elif isinstance(result, _SpecState): 
        result = create_autospec( 
            result.spec, result.spec_set, result.instance, 
            result.parent, result.name 
        ) 
        self._mock_children[name]  = result 
 
    return result 

如您所见,该对象缓存在 _mock_children 字典中。因此每次调用都会返回对象。但是数据会更新。你可以通过运行下面的代码看到

from unittest.mock import Mock 
 
mock = Mock() 
 
mock.a(10) 
mock.a.assert_called_with(10) 
mock.a(2) 
mock.a.assert_called_with(10) 

结果

Traceback (most recent call last): 
  File ".../workbench.py", line 8, in <module> 
    mock.a.assert_called_with(10) 
  File ....lib/python3.7/unittest/mock.py", line 834, in assert_called_with 
    raise AssertionError(_error_message()) from cause 
AssertionError: Expected call: a(10) 
Actual call: a(2) 

所以,是的,对象将是相同的,但对象将具有更新的值