问题描述
python 3,是编码和python的新功能。 我用默认值构建了一个类字典,然后尝试基于该类字典构建嵌套字典,并遇到了意外的行为:
class User:
def __init__(self,*,name=None,age=None,hobbies=[]):
self.name = name
self.age = age
self.hobbies = hobbies
counter = 0
class_dict = {}
# building the nested dicts with default values
for num in range(0,3):
"""
1. referencing "User.__init__.__kwdefaults__"
vs writting the actual dict directly into the nested dict
2. {"name": None,"age": None,"hobbies": []}
"""
class_dict.update({f"key_{num}": User.__init__.__kwdefaults__})
# class_dict.update({f"key_{num}": {"name": None,"hobbies": []}})
print("Blue print: " + str(class_dict))
# updating values in the nested dicts
for x in range(0,3): # simplified loop
dict_key = "key_" + str(counter)
try:
if 1 < 2: # simplified if check
class_dict[dict_key]["name"] = "updated" + str(counter)
print("inside loop: " + str(class_dict))
counter = counter + 1
except:
continue
print("<<< final result: " + str(class_dict) + ">>>") # end-result
-
“ User。 init 。 kwdefaults ”版本将按预期更新循环中正确的嵌套dicts键, 但最终结果是所有3个嵌套字典“名称”键将“ updated2”存储为值。在循环的最后一次迭代中更改的内容在所有嵌套dict中都会更改。
-
实际字典“ {” name“:无,” age“:无,”爱好“:[]}”版本还会按预期更新循环内正确的嵌套dict键。 但是,嵌套字典1中“名称”键的最终结果在嵌套字典2中将“ updated1”和嵌套2中“ updated2”存储为值“ updated0”。
2的最终结果是我的目标,花了我一段时间才找到问题。我不明白为什么两个版本在循环内的行为相同,但最终结果却不同。 是否有dunder / magic方法引用类字典并获得版本2作为最终结果?
解决方法
简化为一个最小的示例,您的代码可以归结为:
d = {'name':None}
dicts = {1: d,2:d}
print(dicts)
# {1: {'name': None},2: {'name': None}}
dicts[1]['name'] = 1
dicts[2]['name'] = 2
print(dicts)
# {1: {'name': 2},2: {'name': 2}}
这并不奇怪,因为dicts[1]
和dicts[2]
是同一个字典d
。
print(d)
# {'name': 2}
请注意,如果您通过创建一些__init__
来调用User(...)
,而您在问题代码中从未这样做过,那么您会遇到Least Astonishment” and the Mutable Default Argument问题。
问题是您要一遍又一遍地为每个键分配相同的下标,可以通过在代码后运行此键来检查它:
for x in range(0,2): # simplified loop
dict_key = "key_" + str(x)
dict_key2 = "key_" + str(x+1)
print(f'subdict of key {x} is subdict of key {x+1}: {class_dict[dict_key] is class_dict[dict_key2]}')
输出为:
subdict of key 0 is subdict of key 1: True
subdict of key 1 is subdict of key 2: True
一种解决方案是将深层副本用作:
import copy
class User:
def __init__(self,*,name=None,age=None,hobbies=[]):
self.name = name
self.age = age
self.hobbies = hobbies
counter = 0
class_dict = {}
# building the nested dicts with default values
for num in range(0,3):
"""
1. referencing "User.__init__.__kwdefaults__"
vs writting the actual dict directly into the nested dict
2. {"name": None,"age": None,"hobbies": []}
"""
class_dict.update({f"key_{num}": copy.deepcopy(User.__init__.__kwdefaults__)})
# class_dict.update({f"key_{num}": {"name": None,"hobbies": []}})
print("Blue print: " + str(class_dict))