如果我想要对象的非递归深层复制,我应该在Python中覆盖复制或深度复制吗?

时间:2017-02-21 23:57:31

标签: python deep-copy

我班级的一个对象有一个列表作为其属性。也就是说,

class T(object):
    def __init__(self, x, y):
        self.arr = [x, y]

复制此对象时,我想要一个单独的列表arr,但需要列表内容的浅表副本(例如xy)。因此,我决定实现自己的复制方法,该方法将重新创建列表,但不会重新创建列表中的项目。但是,我应该拨打此__copy__()还是__deepcopy__()?根据Python语义,哪一个是我所做的正确名称?

我的猜测是__copy__()。如果我打电话给deepcopy(),我希望克隆与原版完全分离。但是,documentation说:

  

深层复制构造一个新的复合对象,然后递归地,   将副本插入到原始文件中找到的对象中。

说“将副本插入其中”代替“将深层副本插入其中”令人困惑,尤其是重点。

3 个答案:

答案 0 :(得分:5)

您在此处实施的正确魔术方法是__copy__

您所描述的行为比副本的默认行为更深(即对于没有实施__copy__的对象),但它不够深入被称为深度复制。因此,您应该实现__copy__以获得所需的行为。

不要错误地认为简单地指定另一个名字会产生"副本":

t1 = T('google.com', 123)
t2 = t1  # this does *not* use __copy__

这只是将另一个名称绑定到同一个实例。相反,__copy__方法被函数挂钩:

import copy
t2 = copy.copy(t1)

答案 1 :(得分:3)

这实际上取决于您所在类的所需行为,这会影响决定覆盖的内容(__copy____deepcopy__)。

一般情况下,copy.deepcopy工作正常,它只是复制所有内容(递归),因此只有在某些属性不能被复制时才需要覆盖它(永远!)。

另一方面,只有当用户(包括您自己)不希望更改传播到复制的实例时,才应定义__copy__。例如,如果您只是包装一个可变类型(如list)或使用可变类型作为实现细节。

然后还有一种情况是要复制的最小属性集没有明确定义。在这种情况下,我也会覆盖__copy__但可能会在其中引发TypeError,并且可能包含一个(或多个)专用的公共copy方法。

但是在我看来,arr计为实现细节,因此我会覆盖__copy__

class T(object):
    def __init__(self, x, y):
        self.arr = [x, y]

    def __copy__(self):
        new = self.__class__(*self.arr)
        # ... maybe other stuff
        return new

只是为了表明它按预期工作:

from copy import copy, deepcopy

x = T([2], [3])
y = copy(x)
x.arr is y.arr        # False
x.arr[0] is y.arr[0]  # True
x.arr[1] is y.arr[1]  # True

x = T([2], [3])
y = deepcopy(x)
x.arr is y.arr        # False
x.arr[0] is y.arr[0]  # False
x.arr[1] is y.arr[1]  # False

关于期望的快速说明:

用户通常希望您可以将实例传递给构造函数以创建最小副本(与__copy__类似或相同)。例如:

lst1 = [1,2,3,4]
lst2 = list(lst1)
lst1 is lst2        # False

某些Python类型具有显式copy方法,(如果存在)应与__copy__相同。这将允许显式传递参数(但我还没有看到这个):

lst3 = lst1.copy()  # python 3.x only (probably)
lst3 is lst1        # False

如果您的课程应该被其他人使用,您可能需要考虑这些要点,但是如果您只想让您的课程使用copy.copy,那么只需覆盖__copy__

答案 2 :(得分:1)

我认为覆盖__copy__是一个好主意,正如其他答案所详细解释的那样。另一种解决方案可能是编写一个显式复制方法,以便对所谓的内容完全清楚:

class T(object):
    def __init__(self, x, y):
        self.arr = [x, y]

    def copy(self):
        return T(*self.arr)

当任何未来的读者看到t.copy()时,他立即知道已经实施了自定义复制方法。缺点当然是它可能不适合使用copy.copy(t)的第三方库。