为啥在 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 对象替换为不同的对象。 ints 无法就地更改值,因此添加构建(或者,可能找到,因为 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

Python 2.7:将零添加到多值字典列表之间的大小差异并将其相加

Python 空值和非空值

python之字典(Dictionary)