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

有人可以向我解释这个吗? 这对我没有任何意义。

我复制一个字典到另一个,编辑第二个,都改变了。 为什么发生这种情况?

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

Python 永远不会隐式地复制对象。 当你设置dict2 = dict1 ,你dict2 = dict1它们指向同一个确切的dict对象,所以当你改变它的时候,它的所有引用都继续引用处于当前状态的对象。

如果你想复制字典(这是罕见的),你必须明确这样做

 dict2 = dict(dict1) 

要么

 dict2 = dict1.copy() 

当你指定dict2 = dict1 ,你并没有制作dict1的副本,它导致dict2只是dict2另一个名字。

要复制字典等可变types,请使用copy模块的copy / deepcopy copy

 import copy dict2 = copy.deepcopy(dict1) 
 >>> 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}} 

你也可以用字典理解来创build一个新的字典。 这避免了导入副本。

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

当然,在python> = 2.7中,你可以这样做:

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

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

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

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

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

所以, dict2 = dict1 ,它会导致dict2dict1引用的对象之间的另一个绑定。

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

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

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

浅拷贝构造一个新的复合对象,然后(尽可能)将引用插入到原始对象中。

深层副本构造一个新的复合对象,然后recursion地将副本插入到原始对象中。

例如,在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']] 

dict2 = dict1不会复制字典。 它只是给程序员第二种方式( dict2 )来引用同一个字典。

python中的每个variables(像dict1str__builtins__这样的东西都是指向机器内某个隐藏的柏拉图“对象”的指针。

如果你设置了dict1 = dict2 ,你只需dict1指向同一个对象(或者内存位置,或者任何你喜欢的类比),就像dict2 。 现在,由dict1引用的对象是由dict1引用的同一个对象。

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

你想要dict1 = copy(dict2) ,或者dict1 = deepcopy(dict2)

copydeepcopy copy的区别? deepcopy将确保dict2的元素(是否指向列表?)也是副本。

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

这也使我感到困惑,因为我是从C背景来的。

在C中,variables是具有定义types的内存中的位置。 分配给variables将数据复制到variables的内存位置。

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

dict1是引用底层字典对象的符号。 将dict1指定给dict2只是分配相同的参考。 通过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) 

在python 3.5+有一个更简单的方法来实现这个使用** unpackaging操作符。 由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 

您可以直接使用:

 dict2 = eval(repr(dict1)) 

对象dict2是dict1的独立副本,因此您可以在不影响dict1的情况下修改dict2。

这适用于任何types的对象。

正如其他人所解释的,内置的dict不会做你想要的。 但在Python2(也可能是3)中,您可以轻松地创build一个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中performance为值(即使它们实际上是不可变的对象)。 当你观察的时候,也请注意strint没有什么神奇的特殊之处。 dict可以用很多相同的方式使用,而且我可以考虑ValueDict有意义的很多情况。

因为python的工作与引用,所以当你做了dict2 = dict1你传递给dict2的引用,这是相同的dict1。 所以,当你在dict1或者dict2中做出改变的时候,你改变了一个引用,并且这两个dicts chages。 对不起,如果我错误的英文。

因为,dict2 = dict1,dict2保留对dict1的引用。 dict1和dict2都指向内存中的相同位置。 在python中使用可变对象时,这只是一个普通的情况。 当你在python中使用可变对象时,你必须小心,因为它很难debugging。 如下面的例子。

  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。 我们从IDvariables中获得,但是我们也无意中更新了my_users的值。 当您使用blocked_ids扩展id时 ,my_users已更新,因为id参考my_users