Python构造函数可变默认参数值跨测试套件被覆盖问题求解
问题核心原因
这是Python中可变类型作为函数默认参数的固有特性,和unittest模块无关,生产环境如果保留错误写法同样会触发该问题。
原理说明
Python的函数默认参数是在函数定义阶段一次性初始化完成的,不会在每次函数调用时重新生成。当你使用列表、字典这类可变类型作为默认参数时,所有未显式传入该参数的函数调用,都会共享同一个可变对象,对该对象的修改会直接作用到默认参数本身。
对应你的场景触发逻辑
你代码中__init__方法的oldInfo={}、newInfo={}用了字典(可变类型)作为默认值,触发流程如下:
- 定义
Info类时,就生成了两个全局共享的空字典,作为oldInfo、newInfo的默认参数 - 实例化
infoA时仅传入了oldInfo参数,newInfo用默认值,因此infoA._newInfo直接指向上述全局共享的默认空字典 - 调用
infoA.combine(infoB)时,会把infoB._newInfo里的v2:2写入infoA._newInfo,也就是直接修改了全局共享的默认参数字典 - 后续实例化
Info时如果不主动传入newInfo,默认用的就是已经被修改过的这个字典,自然会拿到{'v2':2}
修复方案
将可变类型的默认参数替换为None,在函数内部初始化可变对象即可:
def __init__(self, key, *, name=None, id=None, oldInfo=None, newInfo=None, priority=Priority.DEFAULT): self._key = key self._name = name self._id = id self._oldInfo = oldInfo if oldInfo is not None else {} self._newInfo = newInfo if newInfo is not None else {} self._priority = priority assert(not self._oldInfo or type(self._oldInfo) is dict) assert(not self._newInfo or type(self._newInfo) is dict) assert(type(self._priority) is Priority) self._appendix = None
生产环境风险说明
该问题并非unittest模块特有,只要你的代码运行在同一个进程上下文里,按顺序执行实例化对象→修改共享默认参数→再次实例化对象的流程,不管是测试环境还是生产环境都会100%复现。unittest只是因为所有用例在同一进程顺序执行,刚好把这个代码本身的缺陷暴露出来了。
内容的提问来源于stack exchange,提问作者StephenMoody
相关产品推荐
相关产品推荐

