如果我想要我的对象的非递归深度复制,我应该覆盖 Python 中的复制还是深度复制?

If I want non-recursive deep copy of my object, should I override copy or deepcopy in Python?

我的 class 的一个对象有一个列表作为它的属性。也就是说,

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

当这个对象被复制时,我想要一个单独的列表arr,但是列表内容的浅拷贝(例如xy)。因此我决定实现我自己的复制方法,它将重新创建列表而不是其中的项目。但是我应该称它为 __copy__() 还是 __deepcopy__()?根据 Python 语义,哪一个是我所做工作的正确名称?

我的猜测是 __copy__()。如果我调用 deepcopy(),我希望克隆与原始完全分离。但是,documentation 表示:

A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.

用 "inserts copies into it" 代替 "inserts deep copies into it" 会造成混淆,尤其是在强调的时候。

你在这里施展的正确魔法方法是__copy__

您所描述的行为比 copy 的默认行为更深(即对于一个没有费心去实现的对象 __copy__),但它还不够深,无法调用深拷贝。因此,您应该实施 __copy__ 以获得所需的行为。

不要误以为简单地分配另一个名字就可以得到 "copy":

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

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

import copy
t2 = copy.copy(t1)

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

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

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

当任何未来 reader 看到 t.copy() 时,他会立即知道自定义复制方法已经实现。缺点当然是它可能无法很好地与使用 copy.copy(t).

的第三方库一起使用

这实际上取决于您 class 的预期行为,该行为决定了要覆盖的内容(__copy____deepcopy__)。

一般来说 copy.deepcopy 大部分工作正常,它只是复制所有内容(递归地)所以你只需要覆盖它如果有一些属性 不能 被复制(永远!)。

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

还有一种情况是,要复制的最小属性集没有明确定义。在那种情况下,我也会覆盖 __copy__ 但可能会在那里提出 TypeError 并且可能包括一个(或多个)专用 public 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

如果你的 class 应该被其他人使用,你可能需要考虑这些要点,但是如果你只想让你的 class 与 copy.copy 一起工作,那么只需覆盖 __copy__.