如何复制字典并只编辑副本
有人可以向我解释这个吗? 这对我没有任何意义。
我复制一个字典到另一个,编辑第二个,都改变了。 为什么发生这种情况?
>>> 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
,它会导致dict2
和dict1
引用的对象之间的另一个绑定。
如果你想复制一个字典,你可以使用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(像dict1
或str
或__builtins__
这样的东西都是指向机器内某个隐藏的柏拉图“对象”的指针。
如果你设置了dict1 = dict2
,你只需dict1
指向同一个对象(或者内存位置,或者任何你喜欢的类比),就像dict2
。 现在,由dict1
引用的对象是由dict1
引用的同一个对象。
你可以检查: dict1 is dict2
应该是True
。 另外, id(dict1)
应该与id(dict2)
相同。
你想要dict1 = copy(dict2)
,或者dict1 = deepcopy(dict2)
。
copy
和deepcopy
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 – 用于左值修改的干净语法 。 关键的观察是, str
和int
在Python中performance为值(即使它们实际上是不可变的对象)。 当你观察的时候,也请注意str
或int
没有什么神奇的特殊之处。 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 。