浅拷贝,为什么列表没有变化
我正在尝试理解在Python中浅拷贝和深拷贝的区别。我在这里读了很多帖子,它们对我有帮助。不过,我还是不太明白这两者的区别。有没有人能解释一下下面结果的原因?我不理解的结果在评论中有说明。
非常感谢。
import copy
import random
class understand_copy(object):
def __init__(self):
self.listofvalues = [4, 5]
def set_listofvalues(self, pos, value):
self.listofvalues[pos] = value
ins = understand_copy()
newins = copy.copy(ins)
newins.set_listofvalues(1,3)
print "ins = ", ins.listofvalues
print "in newins", newins.listofvalues
# Gives the following output as I would expect based on the explanations.
# prints ins = [4, 3]
# prints newins = [4, 3]
newins.listofvalues.append(5)
print "ins =", ins.listofvalues
print "newins =", newins.listofvalues
# Gives the following output as I would expect based on the explanations.
# prints ins = [4, 3, 5]
# prints newins = [4, 3, 5]
newins.listofvalues = [10, 11]
print "ins = ", ins.listofvalues
print "newins = ", newins.listofvalues
# Gives
# ins = [4, 3, 5]
# newins = [10, 11]
# This is the output that I do not understand.
# Why does ins.listofvalues not change this case.**
2 个回答
ins.listofvalues 没有变化,因为你用一个新的列表替换了 newins 属性,而 append() 方法并不是替换对象,而是修改它。因此,这两个不同的属性现在指向了不同的列表。
你可以在不复制的情况下达到相同的效果:
>>> ins = [1,2,3]
>>> newins = ins
>>>
>>> ins.append(4)
>>> newins
[1, 2, 3, 4]
>>>
>>> ins = [5,6,7]
>>> newins
[1, 2, 3, 4]
所以你理解 copy 和 deepcopy 的概念是正确的,问题在于很多人对 Python 中变量的工作方式有误解。变量是引用,但它们并不是指针。通常来说,把它理解为“标签”会更好。在这个例子中,你把原始列表标记为 ins.listofvalues,然后把那个标签复制到了 newins.listofvalues。但是当你重新给 ins.listofvalues 赋值时,你把那个标签贴到了一个新的对象上。
在Python中,对象的属性是指向对象的引用。所以当你在你的例子中给一个新列表赋值时,其实是改变了属性所指向的对象,而不是改变它的内容。在赋值之前,两个对象的listofvalues
属性都指向同一个列表,但在赋值之后,它们就指向了两个不同的列表。
这就相当于下面的代码:
>>> a = [4, 5]
>>> b = a
>>> b.append(3)
>>> b
[4, 5, 3]
>>> a
[4, 5, 3]
>>> b = [6, 7]
>>> b
[6, 7]
>>> a
[4, 5, 3]
如果你想改变列表的内容,而不是引用,你需要使用切片。也就是说:
>>> a = [4, 5, 3]
>>> b = a
>>> b[:] = [6, 7]
>>> b
[6, 7]
>>> a
[6, 7]
注意:以下内容是基于我对Python 2.6内部机制的理解。因此,这些内容是非常具体于实现的,不过它给你的思维模型可能和语言规则的书写方式非常接近,并且应该适用于任何实现。
在Python中,对象总是通过引用来访问(就像Java那样,而不是C++)。不过,变量名或属性名可以看作是字典中的一个绑定,并且在CPython中是这样实现的(除了局部变量优化、__slots__
的存在,或者通过__getattr__
等暴露的伪属性)。
在Python中,每个对象都有一个私有字典,将每个属性名映射到它的值。而解释器有两个私有字典,分别保存局部变量和全局变量名与其值之间的映射。当你改变一个变量或对象的属性的值时,其实就是在改变对应字典中的绑定。
所以在你的例子中,行为和下面的代码是一样的:
def understand_copy():
return {'listofvalues': [4, 5]}
def deepcopy(obj):
if instance(obj, dict):
copy = {}
for key, value in obj.iteritems():
copy[key] = deepcopy(value) # Note the recursion
return copy
if instance(obj, list):
copy = []
for value in obj:
copy.append(deepcopy(value)) # Note the recursion
return copy
return obj
def copy(obj):
if instance(obj, dict):
copy = {}
for key, value in obj.iteritems():
copy[key] = value # No recursion this time, the copy is shallow
return copy
if instance(obj, list):
copy = []
for value in obj:
copy.append(value) # No recursion this time, the copy is shallow
return copy
return obj
globals = {}
globals['ins'] = understand_copy()
globals['new'] = copy(global['ins'])
# Now globals['ins']['listofvalues']
# and globals['new']['listofvalues']
# reference the same object!
globals['ins']['listofvalues'].__setitem__(0, 3)
globals['ins']['listofvalues'].append(5)
# We are calling function on one object,
# but not changing a binding, so the two
# 'listofvalues' attribute still refers
# to the same object.
globals['ins']['listofvalues'] = [10, 11]
# Now we changed the binding of the name
# in the dictionary 'ins' so now the two
# objects attributes points to different
# lists.