如果我想要我的对象的非递归深度复制,我应该覆盖 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
,但是列表内容的浅拷贝(例如x
和y
)。因此我决定实现我自己的复制方法,它将重新创建列表而不是其中的项目。但是我应该称它为 __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__
.
我的 class 的一个对象有一个列表作为它的属性。也就是说,
class T(object):
def __init__(self, x, y):
self.arr = [x, y]
当这个对象被复制时,我想要一个单独的列表arr
,但是列表内容的浅拷贝(例如x
和y
)。因此我决定实现我自己的复制方法,它将重新创建列表而不是其中的项目。但是我应该称它为 __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__
.