繁体   English   中英

为什么两级字典的值都指向Python 2.7中的同一个对象?

[英]Why are values of a two-level dictionary all pointing to the same object in Python 2.7?

我试图定义一个函数来创建一个双层字典,所以它应该生成格式

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方法有关,但我无法弄清楚。 解释是什么?

注意我需要这个函数来构建大型字典词典,其中每个层都有数百个元素。 我也使用相同的方法来构建一个出现相同问题的三层版本。

您只传入了一个列表对象,而第二层字典仅存储了对该对象的引用。

如果需要存储不同的列表,则需要为每个条目创建一个新列表。 您可以为此使用工厂函数:

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")

您可以使用dict理解来简化您的功能:

def two_tier_dict_init(tier1, tier2, value_factory):
    return {t1: {t2: value_factory() for t2 in tier2} for t1 in tier1}

之所以发生这种情况,是因为您使用作为值传递的相同列表填充所有第二层dicts,并且所有条目都指向相同的列表对象。

一种解决方案是复制每个属性的列表:

dict_name [t1] [t2] =值[:]

这仅在您确定值始终为列表时才有效。

可以复制任何对象(包括嵌套列表和字典)的另一个更通用的解决方案是深度复制:

dict_name [t1] [t2] = copy.deepcopy(value)

如果用不可变对象(如数字或字符串)填充dicts,内部所有条目也会引用同一个对象,但不会发生不良影响,因为数字和字符串是不可变的。

所有值都引用相同的列表对象。 当您在该列表对象上调用append()时,所有字典值似乎都在同时更改。

创建列表更改的副本

        dict_name[t1][t2] = value

        dict_name[t1][t2] = value[:]

或者

        dict_name[t1][t2] = copy.deepcopy(value)

前者将制作浅层(即一层)副本,后者将进行深层复制。

之所以对int起作用,是因为它们是不可变的,并且增强的赋值( +=和朋友)会像普通的赋值语句一样重新命名(可能会返回到同一对象)。 当你这样做:

foobar_dict["foo"]["x"] +=1

你最终用另一个替换旧的int对象。 int不具备就地更改值的功能,因此加法生成(或可能找到,因为CPython实习了某些int)具有新值的另一个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检查),并附加到它,他们仍然是同一个名单。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM