为什么Python中[defaultdict(int)] * 3返回三个指向同一对象的引用?
编辑说明
为什么[defaultdict(int)] * 3会返回三个指向同一对象的引用?
原始标题
Python中将defaultdict列表解包到变量时出现不符合预期的行为
问题描述
将初始化好的defaultdict类型列表解包赋值给多个变量时,运行表现与预期不符,使用的Python版本为3.9.1,复现代码如下:
# 两种写法行为一致,运行正常 a,b,c = [int(), int(), int()] d,e,f = [int()] * 3 # 预期两种写法行为一致,但实际运行表现不同 l,m,p = [defaultdict(int), defaultdict(int), defaultdict(int)] q,r,s = [defaultdict(int)] * 3
完整运行输出:
>>> a,b,c = [int(), int(), int()] >>> a+=4; b+=2; c+=7 >>> a,b,c (4, 2, 7) >>> d,e,f = [int()] * 3 >>> d+=11; e+=8; f+= 41 >>> d,e,f (11, 8, 41) >>> from collections import defaultdict >>> l,m,p = [defaultdict(int), defaultdict(int), defaultdict(int)] >>> l['a']+=1; m['b']+=2; m['c']+=3; >>> l,m,p ( defaultdict(<class 'int'>, {'a': 1}), defaultdict(<class 'int'>, {'b': 2, 'c': 3}), defaultdict(<class 'int'>, {}) ) >>> q,r,s = [defaultdict(int)] * 3 >>> q['a']+=111; r['b']+=222; m['c']+=333; >>> q,r,s ( defaultdict(<class 'int'>, {'a': 111, 'b': 222}), defaultdict(<class 'int'>, {'a': 111, 'b': 222}), defaultdict(<class 'int'>, {'a': 111, 'b': 222}) )
问题解答
这个现象是Python的序列乘法规则、可变/不可变对象的差异共同导致的,核心逻辑如下:
[x] * N的运行规则:序列乘号操作只会复制对象的引用,不会重新执行x的初始化逻辑。也就是说[defaultdict(int)] * 3只会调用一次defaultdict(int)生成实例,然后把这个实例的引用复制3次放到列表里,三个元素本质指向同一个内存地址的对象。- int场景表现正常的原因:int是不可变对象,执行
d += 11时并不会修改原有的int实例,而是会生成一个全新的int对象,再把变量d的指针指向这个新对象,不会影响列表里原来的实例,所以三个变量的修改互不干扰。 - defaultdict表现异常的原因:defaultdict是可变对象,执行
q['a'] += 111是直接修改q指向的defaultdict实例本身的内容,没有创建新对象。而q、r、s三个变量都指向同一个defaultdict实例,所以修改任意一个变量的内容,另外两个也会同步变化。
解决方案
如果需要生成多个独立的defaultdict实例,使用列表推导式写法即可:
q, r, s = [defaultdict(int) for _ in range(3)]
列表推导式的每次循环都会重新执行一次defaultdict(int)初始化,生成三个完全独立的实例,修改时不会互相影响。
内容的提问来源于stack exchange,提问作者rcodemonkey
相关产品推荐
相关产品推荐

