Python unittest.mock调用参数是否按引用拷贝而非保留调用时快照?
问题结论
你遇到的行为不是unittest.mock.Mock的bug,是官方有意设计的默认行为。
原因说明
- Python所有函数参数默认按对象引用传递,
Mock不会额外拷贝传入参数,是出于通用性和性能的考量:deepcopy操作存在不可忽略的性能开销,且部分对象(打开的文件句柄、数据库连接、系统资源实例等)无法被深拷贝,如果默认开启深拷贝逻辑,会导致大量场景下Mock直接抛出异常,无法正常使用。 - 你遇到的单元测试误判问题,本质是断言时机错误:对
Mock调用参数的校验应该在可变参数被修改之前执行,不应该等到参数对象被后续逻辑改动后再做校验,这不符合单元测试的最佳实践。
解决方案
如果你的测试场景需要留存调用时的参数快照,可以采用两种方式处理:
- 调用
Mock后立刻执行断言,或者手动留存参数快照:
from unittest.mock import Mock from copy import deepcopy mock = Mock() l = [1] mock(l) # 方案1:调用后立刻做断言 assert mock.call_args[0][0] == [1] # 方案2:调用后立刻深拷贝留存参数快照 call_snapshot = deepcopy(mock.call_args) l.append(2) # 后续用留存的快照做校验 assert call_snapshot[0][0] == [1]
- 自定义自动深拷贝参数的
Mock子类,适配频繁处理可变参数校验的场景:
from unittest.mock import Mock from copy import deepcopy class SnapshotMock(Mock): def __call__(self, /, *args, **kwargs): # 调用时先深拷贝所有参数再传递给父类存储 copied_args = deepcopy(args) copied_kwargs = deepcopy(kwargs) return super().__call__(*copied_args, **copied_kwargs)
关于默认使用deepcopy的合理性
默认修改call_args为深拷贝存储参数的方案并不合理,该提议已经被Python核心开发团队拒绝:修改默认行为会破坏存量代码的向下兼容性,且无法适配所有参数类型,带来的收益远低于造成的兼容性成本。
内容的提问来源于stack exchange,提问作者Evan Lesmez
相关产品推荐
相关产品推荐

