Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/list/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Python2.7中,为什么两级字典的值都指向同一个对象?_Python_List_Python 2.7_Dictionary_Append - Fatal编程技术网

在Python2.7中,为什么两级字典的值都指向同一个对象?

在Python2.7中,为什么两级字典的值都指向同一个对象?,python,list,python-2.7,dictionary,append,Python,List,Python 2.7,Dictionary,Append,我试图定义一个函数来创建一个两层字典,所以它应该生成格式 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 d

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

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
方法有关,但我无法理解。原因是什么


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

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

创建列表更改的副本的步骤

        dict_name[t1][t2] = value


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

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

如果需要存储不同的列表,则需要为每个条目创建一个新列表。您可以使用factory函数进行以下操作:

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}

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

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

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

仅当您确定值始终是列表时,此选项才有效

另一个更通用的解决方案是深度复制,它适用于任何对象,包括嵌套列表和字典:

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


如果使用不可变对象(如数字或字符串)填充DICT,则在内部,所有条目也将引用同一对象,但不会出现不良效果,因为数字和字符串是不可变的。

这似乎适用于INT的原因是,它们是不可变的,并且是扩展赋值(
+=
和朋友)像普通赋值语句一样重新绑定名称(可能会返回到同一对象)。执行此操作时:

foobar_dict["foo"]["x"] +=1
最后,您将用另一个int对象替换旧的int对象。
int
s无法在适当的位置更改值,因此添加将生成(或者,可能会发现,由于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
检查这一点),你加在后面,它们仍然是相同的列表。

我喜欢工厂方法(+1)。现在一切都有意义了,感谢你指出,价值工厂方法是完美的。反应非常好
foobar_dict["foo"]["x"] +=1
>>> a = b = 1
>>> a is b
True
>>> a += 1
>>> a 
2
>>> b
1