Python递归调用中为何未实例化新Configer对象?
Python可变默认参数导致的意外行为分析
问题重现
你遇到的代码及测试结果如下:
代码示例
class Configer(object): def __init__(self, pops_cfg = {}): self.pops_cfg = pops_cfg def add_pop(self, name, gs): pop_cfg = {} pop_cfg['gs'] = gs if name in self.pops_cfg: self.pops_cfg[name].update(pop_cfg) else: self.pops_cfg[name] = pop_cfg def get(self): return self.pops_cfg def get_config(name): if name == 'I': c = Configer() c.add_pop('I', 100) elif name == 'IE': c = get_config('I') c.add_pop('E', 200) else: raise return c
测试用例及结果
# test 1 a = get_config('I') print(a, id(a), a.get()) # <__main__.Configer object at 0x7fa83615c580> 140360438629760 {'I': {'gs': 100}} # test 2 b = get_config('IE') print(b, id(b), b.get()) # <__main__.Configer object at 0x7fa83603f610> 140360437462544 {'I': {'gs': 100}, 'E': {'gs': 200}} # test 3 c = get_config('I') print(c, id(c), c.get()) # <__main__.Configer object at 0x7fa83603c280> 140360437449344 {'I': {'gs': 100}, 'E': {'gs': 200}}
测试1、2结果符合预期,但测试3与测试1调用逻辑一致,却返回包含两个键的对象。
原因解析
问题根源在Configer.__init__的可变默认参数上:
Python中,函数的默认参数是在函数定义时创建的,而非每次调用时重新生成。对于字典这类可变对象来说,所有不带参数调用Configer()的实例,都会共享同一个字典对象。
具体流程拆解:
- 测试1调用
get_config('I'),创建Configer实例a,a.pops_cfg指向定义__init__时生成的空字典,调用add_pop后该共享字典变为{'I': {'gs': 100}}。 - 测试2调用
get_config('IE'),内部调用get_config('I')创建新实例b,b.pops_cfg仍指向那个已修改的共享字典,调用add_pop后字典变为{'I': {'gs': 100}, 'E': {'gs': 200}}。 - 测试3调用
get_config('I')创建新实例c,c.pops_cfg还是引用这个共享字典,因此返回结果包含两个键。
注意:每个实例的id不同是因为每次都生成了新的Configer对象,但它们的pops_cfg属性都指向同一个共享字典。
修复方案
将可变默认参数替换为None,在__init__内部创建新的空字典,确保每次调用Configer()时生成独立的字典:
class Configer(object): def __init__(self, pops_cfg=None): if pops_cfg is None: pops_cfg = {} self.pops_cfg = pops_cfg # add_pop、get方法保持不变
修改后再运行测试,测试3的返回结果会是{'I': {'gs': 100}},符合预期。
内容的提问来源于stack exchange,提问作者arash
相关产品推荐
相关产品推荐

