关于python:如何复制字典并只编辑副本

How to copy a dictionary and only edit the copy

有人能给我解释一下吗?这对我来说毫无意义。

我把一本字典复制到另一本,然后编辑第二本,两本都变了。为什么会这样?

1
2
3
4
5
6
7
>>> dict1 = {"key1":"value1","key2":"value2"}
>>> dict2 = dict1
>>> dict2
{'key2': 'value2', 'key1': 'value1'}
>>> dict2["key2"] ="WHY?!"
>>> dict1
{'key2': 'WHY?!', 'key1': 'value1'}

python从不隐式复制对象。设置dict2 = dict1时,会使它们引用同一个精确的dict对象,因此当您改变它时,对它的所有引用都会继续引用当前状态下的对象。

如果你想抄写这篇短文(这很少见),你必须用

1
dict2 = dict(dict1)

1
dict2 = dict1.copy()


当您分配dict2 = dict1时,您没有复制dict1的副本,这导致dict2只是dict1的另一个名称。

要复制字典等可变类型,请使用copy模块的copydeepcopy

1
2
3
import copy

dict2 = copy.deepcopy(dict1)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> x={'a': 1, 'b': {'m': 4, 'n': 5, 'o': 6}, 'c': 3}
>>> u=x.copy()
>>> v=dict(x)
>>> import copy
>>> w=copy.deepcopy(x)
>>> x['a']=10
>>> x
{'a': 10, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> u
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> v
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> w
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> x['b']['m']=40
>>> x
{'a': 10, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> u
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> v
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> w
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}


在Python3.5+上,有一种更简单的方法可以通过使用**解包操作符来实现浅拷贝。由PEP 448定义。

1
2
3
4
5
6
7
8
9
>>>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。

1
2
3
4
5
>>>id(dict1)
 178192816

>>>id(dict2)
 178192600

如果需要深度复制,那么copy.deepcopy()仍然是一种方法。


你也可以做一本新的字典来理解字典。这样可以避免导入副本。

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

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

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

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


在python2.7和3中创建dict副本的最好和最简单的方法是…

要创建简单(单级)词典的副本,请执行以下操作:

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

1
2
3
4
5
6
7
8
9
10
11
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()方法。

1
2
3
4
5
6
7
8
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中。*

1
2
3
import copy

my_dict2 = copy.deepcopy(my_dict1)


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

因此,dict2 = dict1dict2dict1所指的对象之间产生了另一种结合。

如果你想复制一个口述,你可以使用copy module。复制模块有两个接口:

1
2
3
4
5
copy.copy(x)
Return a shallow copy of x.

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

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

一个浅拷贝构造一个新的复合对象,然后(在可能的范围内)向其中插入对原始对象的引用。

深度复制构造新的复合对象,然后递归地将原始对象的副本插入其中。

例如,在python 2.7.9中:

1
2
3
4
5
6
7
>>> 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')

结果是:

1
2
3
4
5
6
7
8
>>> 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']]

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

shallow_copy_of_other_dict = {**other_dict}

现在您将有一个"浅"版的other_dict

应用于您的示例:

1
2
3
4
5
6
7
8
>>> dict1 = {"key1":"value1","key2":"value2"}
>>> dict2 = {**dict1}
>>> dict2
{'key1': 'value1', 'key2': 'value2'}
>>> dict2["key2"] ="WHY?!"
>>> dict1
{'key1': 'value1', 'key2': 'value2'}
>>>

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


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

1
2
3
4
5
6
>>> dict1 = {"key1":"value1","key2":"value2"}
>>> dict2 = dict(dict1, key2="WHY?!")
>>> dict1
{'key2': 'value2', 'key1': 'value1'}
>>> dict2
{'key2': 'WHY?!', 'key1': 'value1'}

最初,这也让我困惑,因为我来自C背景。

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

但在Python中,变量的作用更像是指向对象的指针。因此,将一个变量分配给另一个变量并不意味着复制,它只是使该变量名指向同一个对象。


python中的每个变量(比如dict1str__builtins__都是指向机器内部某个隐藏的柏拉图"对象"的指针。

如果设置dict1 = dict2,只需将dict1指向与dict2相同的对象(或内存位置,或您喜欢的任何类比)。现在,dict1引用的对象与dict2引用的对象相同。

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

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

copydeepcopy的区别?deepcopy将确保dict2的元素(您是否在列表中指出?)也是副本。

我不经常使用deepcopy——通常编写需要它的代码是不好的做法(在我看来)。


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


dict1是引用基础字典对象的符号。将dict1分配给dict2只会分配相同的引用。通过dict2符号更改键的值会更改基础对象,这也会影响dict1。这让人困惑。

与引用相比,对不可变值进行推理要容易得多,因此尽可能进行复制:

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

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

1
one_year_later = dict(person, age=26)

1
2
>>> 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对象有很多方法,我只是使用

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


正如其他人所解释的,内置的dict并不能满足您的需求。但是在python2(可能还有3个)中,您可以轻松地创建一个用=复制的ValueDict类,这样就可以确保原始类不会更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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] = ..."

请参考这里讨论的lvalue修改模式:python2.7-clean语法进行lvalue修改。关键的观察是,strint在python中表现为值(即使它们实际上是引擎盖下不可变的对象)。当你观察到这一点时,也请注意,关于strint,没有什么神奇的特别之处。dict的使用方式大致相同,我可以想到很多情况下ValueDict是有意义的。


因为python使用reference,所以当您执行dict2=dict1时,会传递一个对dict2的引用,这与dict1相同。所以,当你改变dict1或dict2时,你会改变一个引用,两个dict都会改变。对不起,如果我把英语搞错了。


很好的解释,我想添加一个最简单的规则,您可以在考虑用=指定相等的python变量时参考它。如果数据类型是不可变的,则不必担心遇到的意外行为。如果数据类型是可变的,您需要确保复制它以防止遇到意外的行为。

不可变数据类型:字符串(字符的元组)、元组

可变数据类型:列表、数组、字典


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

1
2
3
4
5
6
7
8
 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变量得到的,但我们也无意中更新了我的用户的值。当您使用阻止的_id扩展ID时,我的_用户会得到更新,因为ID指向我的_用户。


您可以直接使用:

1
dict2 = eval(repr(dict1))

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

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