为何Python的create_autospec访问下划线字段会触发Property getter且触发两次?
使用create_autospec时访问下划线字段触发属性getter的问题
我用Python的unittest.mock.create_autospec()创建类的mock对象时,遇到了异常行为:直接访问下划线前缀字段mock._val时,会触发val属性的getter方法,我原本以为访问下划线前缀字段不会触发getter。另外,getter方法还被调用了两次。这是预期行为吗?如果是,为什么要这么设计?
复现代码
from unittest.mock import create_autospec def test_strange_behavior(): class MyCls: def __init__(self): self._val = None @property def val(self): print("Inside Get") return self._val @val.setter def val(self, value): print("Inside Set") self._val = value mock = create_autospec(spec=MyCls(), spec_set=True) print("Before") print(mock._val) # 此行触发val的getter方法 print("After")
环境信息
$ python --version Python 3.10.6 $ pip list | grep pytest pytest 7.1.3
运行输出
$ pytest my_test.py -s ============================================================================================================================== test session starts =============================================================================================================================== platform linux -- Python 3.10.6, pytest-7.1.3, pluggy-1.0.0 rootdir: /home/david/PycharmProjects/test plugins: Faker-13.3.4, postgresql-5.0.0 collected 1 item my_test.py Inside Get # 第一次调用 Inside Get # 第二次调用 Before <NonCallableMagicMock name='mock._val' id='140286340235120'> After . =============================================================================================================================== 1 passed in 0.02s ================================================================================================================================
解答
这是预期行为吗?
是,这属于create_autospec结合spec_set=True时的预期行为。
为什么会出现这种情况?
原因在于create_autospec的工作机制:
- 第一次getter调用:当你传入
spec=MyCls()(实例对象)时,create_autospec会遍历该实例的所有可访问属性,包括val(property)和_val(实例属性)。为了模拟val这个property的行为,函数会调用一次其getter方法来获取返回值的类型,以此生成匹配的mock属性,这就触发了第一次"Inside Get"。 - 第二次getter调用:当
spec_set=True时,mock会严格限制只能访问spec对象中存在的属性。访问mock._val时,mock会先验证该属性是否存在于spec实例中。在这个验证过程中,mock的内部实现会间接触发val的getter——这是因为mock在处理属性检查时,依赖Python的属性查找逻辑,而val的getter恰好关联了_val字段,导致调用链被触发。
如何避免这种行为?
建议将spec参数传入类而非实例,即:
mock = create_autospec(spec=MyCls, spec_set=True)
这样create_autospec会模拟类的结构而非实例的具体属性,访问mock._val时就不会触发val的getter方法了。
内容的提问来源于stack exchange,提问作者salius
相关产品推荐
相关产品推荐

