为啥在 Python 2.7 中两级字典的值都指向同一个对象?
Posted
技术标签:
【中文标题】为啥在 Python 2.7 中两级字典的值都指向同一个对象?【英文标题】:Why are values of a two-level dictionary all pointing to the same object in Python 2.7?为什么在 Python 2.7 中两级字典的值都指向同一个对象? 【发布时间】:2014-02-21 08:57:45 【问题描述】:我试图定义一个函数来创建一个两层字典,所以它应该产生格式
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
方法有关,但我无法弄清楚。有什么解释?
注意我需要这个函数来构建大型词典,其中每一层都有数百个元素。我也使用相同的方法构建了一个三层版本,但出现了同样的问题。
【问题讨论】:
【参考方案1】:你只传入了一个列表对象,而你的第二层字典只存储了对那个对象的引用。
如果您需要存储不同的列表,则需要为每个条目创建一个新列表。您可以为此使用工厂函数:
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)
让它创建空列表。您可以在此处为值工厂使用任何可调用对象,包括 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)。 现在一切都说得通了,感谢您指出,value_factory 方法是完美的。反应很好【参考方案2】:这似乎与 int 一起工作的原因是因为它们是不可变的,并且增强的赋值(+=
和朋友)会像普通的赋值语句一样重新绑定名称(它可能只是返回到同一个对象)。当你这样做时:
foobar_dict["foo"]["x"] +=1
您最终将旧的 int 对象替换为不同的对象。 int
s 无法就地更改值,因此添加构建(或者,可能找到,因为 CPython 实习生某些整数)具有新值的不同 int。
因此,即使 foobar_dict["foo"]["x"]
和 foobar_dict["foo"]["y"]
开始时使用相同的 int(并且确实如此),添加到其中一个会使它们现在包含 不同 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
检查),并且您附加到它,它们仍然是同一个列表。
【讨论】:
【参考方案3】:这是因为您使用作为值传递的相同列表填充所有第二层字典,并且所有条目都指向同一个列表对象。
一种解决方案是在每个属性处复制列表:
dict_name[t1][t2] = value[:]
这仅在您确定该值始终是一个列表时才有效。
另一个更通用的解决方案是深度复制,适用于任何对象,包括嵌套列表和字典:
dict_name[t1][t2] = copy.deepcopy(value)
如果您使用不可变对象(如数字或字符串)填充字典,则在内部所有条目也将引用同一个对象,但不会发生不良影响,因为数字和字符串是不可变的。
【讨论】:
【参考方案4】:所有的值都指向同一个列表对象。当您对该列表对象调用 append()
时,所有字典值似乎同时发生变化。
创建列表更改的副本
dict_name[t1][t2] = value
到
dict_name[t1][t2] = value[:]
或到
dict_name[t1][t2] = copy.deepcopy(value)
前者会做浅拷贝(即一层),后者会做深拷贝。
【讨论】:
以上是关于为啥在 Python 2.7 中两级字典的值都指向同一个对象?的主要内容,如果未能解决你的问题,请参考以下文章
python 一个由字典构成的列表,修改其中1个字典的键的值,却把该列表所有字典相同的键的值都一起修改了?
Python怎么排序字典,当字典里的值都是一个类的实例化对象,键为不同的字符串,根据实例化对象的值x排序
python调用mysql中的自定义变量,为啥返回的值都是None