我试图定义一个函数来创建一个双层字典,所以它应该生成格式
dict = {tier1:{tier2:value}}.
代码是:
def two_tier_dict_init(tier1,tier2,value):
dict_name = {}
for t1 in tier1:
dict_name[t1] = {}
for t2 in tier2:
dict_name[t1][t2] = value
return dict_name
以下示例......
tier1 = ["foo","bar"]
tier2 = ["x","y"]
value = []
foobar_dict = two_tier_dict_init(tier1,tier2,value)
在它的表面上产生了我想要的东西:
foobar_dict = {'foo':{'x': [],'y':[]},
'bar':{'x': [],'y':[]}} }
但是,当附加任何值,如
foobar_dict["foo"]["x"].append("thing")
附加所有值,结果为:
foobar_dict = {'foo':{'x': ["thing"],'y':["thing"]},
'bar':{'x': ["thing"],'y':["thing"]}}
起初我假设由于我的定义构建字典的方式,所有值都指向内存中的相同空间,但我无法弄清楚为什么会出现这种情况。然后我发现如果我将值从空列表更改为整数,当我执行以下操作时,
foobar_dict["foo"]["x"] +=1
仅更改所需的值。
因此,我必须得出结论,这与list.append
方法有关,但我无法弄明白。解释是什么?
N.B。我需要这个函数来构建大型字典词典,其中每个层都有数百个元素。我也使用相同的方法来构建一个三层版本,同时出现相同的问题。
答案 0 :(得分:6)
您只传入了一个列表对象,而您的第二层词典仅存储了对该一个对象的引用。
如果您需要存储不同的列表,则需要为每个条目创建一个新列表。您可以使用工厂函数:
def two_tier_dict_init(tier1, tier2, value_factory):
dict_name = {}
for t1 in tier1:
dict_name[t1] = {}
for t2 in tier2:
dict_name[t1][t2] = value_factory()
return dict_name
然后使用:
two_tier_dict_init(tier1, tier2, list)
让它创建空列表。你可以在这里使用任何callable作为值factory,如果你想存储一个像字符串或整数这样的不可变对象,你可以使用lambda
:
two_tier_dict_init(tier1, tier2, lambda: "I am shared but immutable")
您可以使用字典理解来简化您的功能:
def two_tier_dict_init(tier1, tier2, value_factory):
return {t1: {t2: value_factory() for t2 in tier2} for t1 in tier1}
答案 1 :(得分:1)
之所以发生这种情况,是因为您使用作为值传递的相同列表填充所有第二层dicts,并且所有条目都指向相同的列表对象。
一种解决方案是复制每个归因的列表:
dict_name [t1] [t2] =值[:]
仅当您确定值始终为列表时才有效。
另一个更通用的解决方案,适用于任何对象,包括嵌套列表和字典,是深度复制:
dict_name [t1] [t2] = copy.deepcopy(value)
如果用不可变对象(如数字或字符串)填充dicts,内部所有条目也会引用同一个对象,但不会发生不良影响,因为数字和字符串是不可变的。
答案 2 :(得分:0)
所有值都引用相同的列表对象。当您在该列表对象上调用append()
时,所有字典值似乎都会同时更改。
创建列表更改的副本
dict_name[t1][t2] = value
到
dict_name[t1][t2] = value[:]
或
dict_name[t1][t2] = copy.deepcopy(value)
前者将制作浅层(即一级)副本,后者将进行深层复制。
答案 3 :(得分:0)
这似乎与int一起使用的原因是因为它们是不可变的,并且增强的赋值(+=
和朋友)像普通的赋值语句一样重命名(它可能会回到同一个对象)。当你这样做时:
foobar_dict["foo"]["x"] +=1
你最终用另一个替换旧的int对象。 int
没有能力就地更改值,因此添加构建(或者,可能会发现,因为CPython实例化某些int)与新值不同的int。
因此,即使foobar_dict["foo"]["x"]
和foobar_dict["foo"]["y"]
以相同的int开头(并且他们也这样做),添加其中一个使得它们现在包含不同的整数。
如果您尝试使用更简单的变量,可以看到这种差异:
>>> a = b = 1
>>> a is b
True
>>> a += 1
>>> a
2
>>> b
1
另一方面,list
是可变的,和调用append
不会进行任何重新绑定。因此,正如您所怀疑的那样,如果foobar_dict["foo"]["x"]
和foobar_dict["foo"]["y"]
是相同的列表(并且它们是 - 请使用is
检查),并且您附加到它,它们仍然是相同的列表。