浅拷贝,为什么列表没有变化

4 投票
2 回答
3468 浏览
提问于 2025-04-16 09:02

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

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 赋值时,你把那个标签贴到了一个新的对象上。

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.

撰写回答