浅拷贝,为什么列表不会改变

4
我正在尝试理解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个回答

8
在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.

2
请注意,切片仅适用于一维列表。如果我有 a = [1,2,[3,4,5]];b = a[:]; a[2][0] = 30; 那么 a 和 b 都将打印出 [1,2,[30,4,5]]。 - sahhhm

3

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时,您将该标签粘贴到一个新对象上。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接