You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让unittest.mock._patch实例支持下标访问与迭代?

问题与解决方案:unittest.mock.patch模拟可迭代/可下标访问类的问题

问题背景

我有一个实现了下标访问与迭代功能的Foo类:

from typing import List

class Foo:
    def __init__(self, iterable: List[...]):
        self.iterable = iterable

    def __getitem__(self, i: int):
        return self.iterable[i]

    def __iter__(self):
        return iter(self.iterable)

该类的实例支持直接下标访问和迭代:

>>> foo = Foo(iterable=['a', 'b', 'c'])
>>> foo[-1]
'c'
>>> for x in foo:
...    print(x)
...
a
b
c

我尝试用unittest.mock.patch测试该类,代码如下:

from unittest.mock import patch

def test_foo():
    mock_foo = patch('my_module.Foo', autospec=True)
    mock_foo.iterable = ['a', 'b', 'c']
    mock_foo.__getitem__ = lambda self, i: self.iterable[i]
    mock_foo.__iter__ = lambda self: iter(self.iterable)

但直接对mock_foo进行下标访问或迭代时失败:

>>> mock_foo[-1]
TypeError: '_patch' object is not subscriptable
>>> list(mock_foo)
TypeError: '_patch' object is not iterable

奇怪的是,直接调用魔法方法却能正常工作:

>>> mock_foo.__getitem__(mock_foo, -1)
'c'
>>> list(mock_foo.__iter__(mock_foo))
['a', 'b', 'c']

原因分析

出现这种不一致的核心原因是:

  • patch('my_module.Foo')返回的**_patch对象是一个补丁管理器**,它的作用是临时替换目标类,而非模拟的Foo类实例或类本身。
  • Python的特殊魔法方法(如__getitem__、__iter__)调用逻辑是:当使用obj[i]或iter(obj)时,Python会在obj的类层面查找对应的方法。直接给_patch对象赋值__getitem__,并不会让Python将其识别为支持下标访问的类型——因为_patch类本身没有实现这些方法。
  • 直接调用mock_foo.__getitem__(mock_foo, -1)能工作,只是因为手动调用了赋值的函数,但这不符合Python对特殊方法的触发逻辑。

解决方法与更优实践

方法1:通过上下文管理器/装饰器获取模拟类,配置实例行为

正确的做法是使用patch的上下文管理器或装饰器,获取被替换的模拟类对象,然后配置其return_value(即模拟类实例的行为):

from unittest.mock import patch

def test_foo():
    with patch('my_module.Foo', autospec=True) as mock_foo_class:
        # 获取模拟类的实例对象(调用Foo()会返回的对象)
        mock_instance = mock_foo_class.return_value
        
        # 配置实例的属性和魔法方法
        mock_instance.iterable = ['a', 'b', 'c']
        mock_instance.__getitem__.side_effect = lambda i: mock_instance.iterable[i]
        mock_instance.__iter__.return_value = iter(mock_instance.iterable)
        
        # 正常使用模拟实例
        assert mock_instance[-1] == 'c'
        assert list(mock_instance) == ['a', 'b', 'c']

方法2:直接使用真实实例作为补丁返回值(最简单)

如果Foo类的实例化没有副作用,直接用真实的Foo实例作为patch的return_value是最简洁的方式,完全复现原类行为:

from unittest.mock import patch
from my_module import Foo

def test_foo():
    real_foo = Foo(['a', 'b', 'c'])
    with patch('my_module.Foo', return_value=real_foo):
        # 调用my_module.Foo()会返回真实实例
        foo = Foo()
        assert foo[-1] == 'c'
        assert list(foo) == ['a', 'b', 'c']

方法3:用MagicMock手动模拟实例

如果需要更灵活的控制(比如断言方法调用次数),可以用MagicMock创建模拟实例,再让patch指向它:

from unittest.mock import patch, MagicMock

def test_foo():
    mock_instance = MagicMock()
    mock_instance.iterable = ['a', 'b', 'c']
    # 配置下标访问逻辑
    mock_instance.__getitem__.side_effect = lambda i: mock_instance.iterable[i]
    # 配置迭代逻辑
    mock_instance.__iter__.return_value = iter(mock_instance.iterable)
    
    with patch('my_module.Foo', return_value=mock_instance):
        from my_module import Foo
        foo = Foo()
        assert foo[-1] == 'c'
        assert list(foo) == ['a', 'b', 'c']
        # 断言方法调用次数
        mock_instance.__getitem__.assert_called_once_with(-1)

内容的提问来源于stack exchange,提问作者themagicalkamja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 11:43:27