如何复制字典并仅编辑副本


852

有人可以向我解释一下吗?这对我来说毫无意义。

我将字典复制到另一个字典中,然后编辑第二个字典,并且两者都已更改。为什么会这样呢?

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict1
>>> dict2
{'key2': 'value2', 'key1': 'value1'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key2': 'WHY?!', 'key1': 'value1'}

3
PythonTutor非常适合可视化Python参考。这是最后一步的代码。您可以看到dict1dict2指向相同的字典。
wjandrea

Answers:


881

Python 绝不会隐式复制对象。设置时dict2 = dict1,将使它们引用同一精确的dict对象,因此,在对它进行突变时,对其的所有引用都将始终引用该对象的当前状态。

如果要复制字典(这种情况很少见),则必须使用

dict2 = dict(dict1)

要么

dict2 = dict1.copy()

26
最好说“ dict2和dict1指向同一个字典”,您不是在更改dict1或dict2,而是更改它们指向的内容。
GrayWizardx

275
还要注意,dict.copy()是浅层的,如果其中有嵌套列表/等,则更改将同时应用于两者。IIRC。深度复制将避免这种情况。
威尔

16
python从来没有隐式地复制对象是不太正确的。原始数据类型(例如int,float和bool)也被视为对象(只需执行a dir(1)即可看到),但它们是隐式复制的。
丹尼尔·库尔曼2012年

17
@danielkullmann,我想您可能会对基于其他语言工作方式的Python有误解。在Python中,a)没有“原始数据类型”的概念。intfloatbool实例是真实的Python对象,并且b)这些类型的对象在传递它们时不会隐式复制,不能肯定地在语义Python级别上使用,甚至不能作为CPython中的实现细节使用。
Mike Graham

39
诸如“深复制被认为是有害的”之类毫无根据的言论是无济于事的。所有其他条件相同,浅拷贝一个复杂的数据结构是显著更可能产生较深刻的复制相同的结构意想不到的边缘情况的问题。通过修改修改原始对象的副本不是副本。这是一个错误。因此,大多数用例绝对调用copy.deepcopy()而不是dict()dict.copy()。与这个答案不同,Imran简洁答案在理智的右边。
塞西尔·库里

645

分配时dict2 = dict1,您并没有复制该文件的副本dict1,导致dict2它只是它的另一个名称dict1

要复制字典等可变类型,请使用copy/ deepcopycopy模块。

import copy

dict2 = copy.deepcopy(dict1)

80
对于我曾经使用过的任何词典,我都需要使用Deepcopy ...由于一个错误,我损失了几个小时,原因是我没有获得嵌套词典的完整副本,而对嵌套条目的更改影响了原始词典。 。
flutefreak14年

7
同样在这里。deepcopy()可以解决问题。通过在原始事件的“副本”中添加时间戳,将旋转的缓存中的嵌套字典弄乱了。谢谢!
fxstein 2015年

8
实际上,这应该被标记为正确答案。这个答案很笼统,也适用于字典词典。
orezvani

30
这应该是公认的答案。嵌入嵌套在当前接受的答案的注释部分中的未经证实的“深复制被认为是有害的”,在复制嵌套字典(例如此处记录的字典)时,公然引发了同步问题,因此应对此提出质疑。
塞西尔·库里

在复杂的字典结构中,deepcopy是必经之路。dict1.copy()只是将键的值复制为引用而不是对象。
Rohith N

182

虽然dict.copy()dict(dict1)生成副本,但它们只是浅表副本。如果要拷贝,copy.deepcopy(dict1)则是必需的。一个例子:

>>> source = {'a': 1, 'b': {'m': 4, 'n': 5, 'o': 6}, 'c': 3}
>>> copy1 = x.copy()
>>> copy2 = dict(x)
>>> import copy
>>> copy3 = copy.deepcopy(x)
>>> source['a'] = 10  # a change to first-level properties won't affect copies
>>> source
{'a': 10, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy1
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy2
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy3
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> source['b']['m'] = 40  # a change to deep properties WILL affect shallow copies 'b.m' property
>>> source
{'a': 10, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy1
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy2
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy3  # Deep copy's 'b.m' property is unaffected
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}

关于浅层副本与深层副本,来自Python copy模块docs

浅表复制和深度复制之间的区别仅与复合对象(包含其他对象的对象,如列表或类实例)有关:

  • 浅表副本构造一个新的复合对象,然后(在可能的范围内)将对原始对象中找到的对象的引用插入其中。
  • 深层副本将构造一个新的复合对象,然后递归地将原始对象中发现的对象的副本插入其中。

2
这应该是正确的答案,因为它不会显式地遍历该字典,并且可以用于其他主要结构。
Nikkolasg,2015年

27
只是澄清w=copy.deepcopy(x)一下:是关键所在。
alcoholiday

dict2 = dict1和之间有什么区别dict2 = copy.deepcopy(dict1)
TheTank

1
@ TheTank,y = x使两个名称(引用)引用相同的对象,即“ y is x”为True。通过x对对象所做的任何更改等效于通过y进行的相同更改。但是,u,v,w是对新的不同对象的引用,尽管这些对象在实例化期间具有从x复制的值。至于u,v(浅拷贝)和w(深拷贝)之间的区别,请检查docs.python.org/2/library/copy.html
gpanda

63

在python 3.5+上,有一种更简单的方法可以通过使用**解包运算符来实现浅表副本。由Pep 448定义。

>>>dict1 = {"key1": "value1", "key2": "value2"}
>>>dict2 = {**dict1}
>>>print(dict2)
{'key1': 'value1', 'key2': 'value2'}
>>>dict2["key2"] = "WHY?!"
>>>print(dict1)
{'key1': 'value1', 'key2': 'value2'}
>>>print(dict2)
{'key1': 'value1', 'key2': 'WHY?!'}

**将字典解包为新字典,然后将其分配给dict2。

我们还可以确认每个词典都有不同的ID。

>>>id(dict1)
 178192816

>>>id(dict2)
 178192600

如果需要深层副本,那么仍然可以使用copy.deepcopy()


3
这看起来非常像C ++中的指针。很适合完成任务,但是在可读性方面,我倾向于不喜欢这种类型的运算符。
埃内斯托

1
它确实有一种c'ish的外观...但是将多个词典合并在一起时,语法看起来确实很流畅。
PabTorre

2
请注意,它仅执行浅表复制。
塞巴斯蒂安·德莱斯特勒

您说得对@SebastianDressler,我会进行调整。thnx。
PabTorre

2
如果您要创建带有一些香料的副本,则很有用:dict2 = {**dict1, 'key3':'value3'}
evg656e '18

47

最好的和最简单的方法创建一个副本一个的字典中都Python的2.7和3是...

要创建简单(单级)字典的副本:

1.使用dict()方法,而不是生成指向现有dict的引用。

my_dict1 = dict()
my_dict1["message"] = "Hello Python"
print(my_dict1)  # {'message':'Hello Python'}

my_dict2 = dict(my_dict1)
print(my_dict2)  # {'message':'Hello Python'}

# Made changes in my_dict1 
my_dict1["name"] = "Emrit"
print(my_dict1)  # {'message':'Hello Python', 'name' : 'Emrit'}
print(my_dict2)  # {'message':'Hello Python'}

2.使用python字典的内置update()方法。

my_dict2 = dict()
my_dict2.update(my_dict1)
print(my_dict2)  # {'message':'Hello Python'}

# Made changes in my_dict1 
my_dict1["name"] = "Emrit"
print(my_dict1)  # {'message':'Hello Python', 'name' : 'Emrit'}
print(my_dict2)  # {'message':'Hello Python'}

要创建嵌套或复杂字典的副本:

使用内置的复制模块,该模块提供通用的浅层和深层复制操作。Python 2.7和3中都提供了此模块。*

import copy

my_dict2 = copy.deepcopy(my_dict1)

6
我相信dict()会创建浅表副本,而不是深表副本。这意味着,如果您有嵌套,dict则外部dict将是副本,但内部dict将是对原始内部dict的引用。
shmuels

@shmuels是的,这两种方法都将创建浅表副本,而不是深表副本。请参阅更新后的答案。
AKay Nirala

37

您也可以使用字典理解功能来制作新字典。这样可以避免导入副本。

dout = dict((k,v) for k,v in mydict.items())

当然,在python> = 2.7中,您可以执行以下操作:

dout = {k:v for k,v in mydict.items()}

但是对于向后兼容,顶级方法更好。


4
如果您想更好地控制复制的方式和内容,此功能特别有用。+1
ApproachingDarknessFish

14
请注意,此方法不会执行深层复制,并且如果您希望进行浅层复制而不需要控制要复制的密钥,d2 = dict.copy(d1)则也不需要任何导入。
JarekPiórkowski15年

1
@JarekPiórkowski:或者您可以调用类似方法的方法:d2 = d1.copy()
Azat Ibrakov,

请注意,在第一个示例中您不需要理解。dict.items已经返回可迭代的键/值对。因此,您可以使用dict(mydict.items())(也可以使用dict(mydict))。如果您想过滤条目,那么理解可能会很有用。
保罗·鲁尼

22

除了提供的其他解决方案外,您还可以**将字典集成到一个空字典中,例如,

shallow_copy_of_other_dict = {**other_dict}

现在,您将拥有的“浅”副本other_dict

应用于您的示例:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = {**dict1}
>>> dict2
{'key1': 'value1', 'key2': 'value2'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key1': 'value1', 'key2': 'value2'}
>>>

指针:浅拷贝和深拷贝之间的区别


1
这导致浅表副本,而不是深表副本。
sytech '18

1
我正在尝试这个,但是遇到了麻烦。这仅适用于python 3.5及更高版本。python.org/dev/peps/pep-0448
ThatGuyRob

19

Python中的赋值语句不复制对象,它们在目标和对象之间创建绑定。

因此,dict2 = dict1它会在dict2dict1引用的对象之间产生另一个绑定。

如果要复制字典,可以使用copy module。复制模块有两个接口:

copy.copy(x)
Return a shallow copy of x.

copy.deepcopy(x)
Return a deep copy of x.

浅表复制和深度复制之间的区别仅与复合对象(包含其他对象的对象,如列表或类实例)有关:

浅拷贝构造新化合物对象,然后(在可能的范围)插入到它的对象引用原始发现。

深层副本构造新化合物的对象,然后,递归地,插入拷贝到它的目的在原始发现。

例如,在python 2.7.9中:

>>> import copy
>>> a = [1,2,3,4,['a', 'b']]
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
>>> a.append(5)
>>> a[4].append('c')

结果是:

>>> a
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> b
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> c
[1, 2, 3, 4, ['a', 'b', 'c']]
>>> d
[1, 2, 3, 4, ['a', 'b']]

10

您可以通过dict使用其他关键字参数调用构造函数来一次性复制和编辑新构造的副本:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict(dict1, key2="WHY?!")
>>> dict1
{'key2': 'value2', 'key1': 'value1'}
>>> dict2
{'key2': 'WHY?!', 'key1': 'value1'}

9

最初,这也使我感到困惑,因为我来自C语言。

在C语言中,变量是内存中定义类型的位置。分配给变量会将数据复制到变量的存储位置。

但是在Python中,变量的作用更像是指向对象的指针。因此,将一个变量分配给另一个变量不会产生副本,只会使该变量名称指向同一对象。


5
python变量的行为更像c ++引用
Ruggero Turra 2010年

7
因为Python中的一切都是对象! diveintopython.net/getting_to_know_python/… (是的,此响应已晚了很多年,但也许对某人
有用

1
我相信Python语言的语义说没有“变量”。它们被称为“命名参考”;表示对对象的引用是代码中的语法字符串。一个对象可以有许多对它的命名引用。不变的对象(如int和float和str实例)在每个进程中只有一个实例。myvalue = 1 myvalue = 2时,内存中的int不会更改为2或同一内存地址处的其他值
DevPlayer

7

python中的每个变量(类似于dict1str或的东西__builtins__都是指向机器内部某些隐藏的柏拉图“对象”的指针。

如果设置dict1 = dict2,则只需指向dict1与相同的对象(或内存位置,或类似的对象)dict2。现在,所引用的对象与所引用的对象dict1相同dict2

您可以检查:dict1 is dict2应该是True。另外,id(dict1)应与相同id(dict2)

您想要dict1 = copy(dict2)dict1 = deepcopy(dict2)

copy和之间的区别deepcopydeepcopy将确保dict2(您是否将其指向列表?)的元素也是副本。

我用的不是deepcopy很多-在我看来,编写需要它的代码通常是不明智的做法。


我只是意识到我需要一直使用Deepcopy,这样当我复制嵌套字典并开始修改嵌套条目时,效果只会在副本上发生,而不是在原始副本上发生。
flutefreak14年

6

dict1是引用基础字典对象的符号。分配dict1dict2仅分配相同的参考。通过dict2符号更改键的值会更改基础对象,这也会影响dict1。这很混乱。

关于不可变值的推理要比引用容易得多,因此请尽可能制作副本:

person = {'name': 'Mary', 'age': 25}
one_year_later = {**person, 'age': 26}  # does not mutate person dict

在语法上与以下内容相同:

one_year_later = dict(person, age=26)

5

dict2 = dict1不复制字典。它只是为程序员提供了第二种方法(dict2)来引用同一词典。


5
>>> dict2 = dict1
# dict2 is bind to the same Dict object which binds to dict1, so if you modify dict2, you will modify the dict1

复制Dict对象的方法很多,我只是简单地使用

dict_1 = {
           'a':1,
           'b':2
         }
dict_2 = {}
dict_2.update(dict_1)

12
dict_2 = dict_1.copy()更加高效和合乎逻辑。
让·弗朗索瓦·法布尔

2
请注意,如果您在dict1内有一个dict,则使用dict_1.copy()对dict_2中的内部dict所做的更改也将应用于dict_1中的内部dict。在这种情况下,应改用copy.deepcopy(dict_1)。

1

正如其他人所解释的,内置dict功能无法满足您的需求。但是在Python2中(可能还有3个),您可以轻松地创建一个ValueDict用于复制的类,=因此可以确保原始版本不会更改。

class ValueDict(dict):

    def __ilshift__(self, args):
        result = ValueDict(self)
        if isinstance(args, dict):
            dict.update(result, args)
        else:
            dict.__setitem__(result, *args)
        return result # Pythonic LVALUE modification

    def __irshift__(self, args):
        result = ValueDict(self)
        dict.__delitem__(result, args)
        return result # Pythonic LVALUE modification

    def __setitem__(self, k, v):
        raise AttributeError, \
            "Use \"value_dict<<='%s', ...\" instead of \"d[%s] = ...\"" % (k,k)

    def __delitem__(self, k):
        raise AttributeError, \
            "Use \"value_dict>>='%s'\" instead of \"del d[%s]" % (k,k)

    def update(self, d2):
        raise AttributeError, \
            "Use \"value_dict<<=dict2\" instead of \"value_dict.update(dict2)\""


# test
d = ValueDict()

d <<='apples', 5
d <<='pears', 8
print "d =", d

e = d
e <<='bananas', 1
print "e =", e
print "d =", d

d >>='pears'
print "d =", d
d <<={'blueberries': 2, 'watermelons': 315}
print "d =", d
print "e =", e
print "e['bananas'] =", e['bananas']


# result
d = {'apples': 5, 'pears': 8}
e = {'apples': 5, 'pears': 8, 'bananas': 1}
d = {'apples': 5, 'pears': 8}
d = {'apples': 5}
d = {'watermelons': 315, 'blueberries': 2, 'apples': 5}
e = {'apples': 5, 'pears': 8, 'bananas': 1}
e['bananas'] = 1

# e[0]=3
# would give:
# AttributeError: Use "value_dict<<='0', ..." instead of "d[0] = ..."

请参考此处讨论的左值修改模式:Python 2.7-左值修改的纯语法。关键的观察是,strint表现为在Python值(即使它们实际上是引擎盖下的不可变对象)。当您观察到这一点时,也请注意,关于str或,没有什么神奇的特别之处intdict可以以几乎相同的方式使用,我可以想到很多ValueDict有意义的情况。


0

以下代码,是遵循json语法的字典的,比deepcopy快3倍以上

def CopyDict(dSrc):
    try:
        return json.loads(json.dumps(dSrc))
    except Exception as e:
        Logger.warning("Can't copy dict the preferred way:"+str(dSrc))
        return deepcopy(dSrc)

0

尝试深复制类w / o将其分配给变量的字典属性时,遇到了一种特殊的行为

new = copy.deepcopy(my_class.a)不起作用,即修改new修改my_class.a

但是,如果您这样做old = my_class.a,那么new = copy.deepcopy(old)它会完美运行,即修改new不会影响my_class.a

我不确定为什么会发生这种情况,但希望它可以节省一些时间!:)


那么,您如何制作的深层副本my_class.a
安东尼

不是最好的方法。良好的反应如下。
David Beauchemin

-1

因为dict2 = dict1,dict2保存了对dict1的引用。dict1和dict2都指向内存中的同一位置。这是在python中使用可变对象时的正常情况。使用python中的可变对象时,必须小心,因为它很难调试。如下面的例子。

 my_users = {
        'ids':[1,2],
        'blocked_ids':[5,6,7]
 }
 ids = my_users.get('ids')
 ids.extend(my_users.get('blocked_ids')) #all_ids
 print ids#output:[1, 2, 5, 6, 7]
 print my_users #output:{'blocked_ids': [5, 6, 7], 'ids': [1, 2, 5, 6, 7]}

此示例意图是获取所有用户ID,包括被阻止的ID。我们是从ids变量获得的,但是我们也无意间更新了my_users的值。当你扩展的IDSblocked_ids my_users得到了更新,因为IDS参考my_users


-1

使用for循环进行复制:

orig = {"X2": 674.5, "X3": 245.0}

copy = {}
for key in orig:
    copy[key] = orig[key]

print(orig) # {'X2': 674.5, 'X3': 245.0}
print(copy) # {'X2': 674.5, 'X3': 245.0}
copy["X2"] = 808
print(orig) # {'X2': 674.5, 'X3': 245.0}
print(copy) # {'X2': 808, 'X3': 245.0}

1
这仅适用于简单词典。为什么不使用deepcopy明确为此目的而构建的?
安东尼

不是最好的方法。良好的反应如下。
David Beauchemin

-6

您可以直接使用:

dict2 = eval(repr(dict1))

其中对象dict2是dict1的独立副本,因此您可以修改dict2而不会影响dict1。

这适用于任何类型的对象。


4
该答案是错误的,不应使用。例如,用户定义的类可能没有适当__repr__的值可以通过eval进行重构,对象的类也可能不在当前要调用的范围内。即使坚持使用内置类型,如果同一对象存储在多个键下,则失败,因为dict2这将有两个单独的对象。dict1包含自身的自引用字典将包含Ellipsis。最好使用dict1.copy()
Eldritch Cheese

不能期望对象(或“值”)始终用字符串来忠实地表示,无论如何都不以通常的人类可读方式表示。
阿列克谢
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.