我班级的一个对象有一个列表作为其属性。也就是说,
class T(object):
def __init__(self, x, y):
self.arr = [x, y]
复制此对象时,我想要一个单独的列表arr
,但需要列表内容的浅表副本(例如x
和y
)。因此,我决定实现自己的复制方法,该方法将重新创建列表,但不会重新创建列表中的项目。但是,我应该拨打此__copy__()
还是__deepcopy__()
?根据Python语义,哪一个是我所做的正确名称?
我的猜测是__copy__()
。如果我打电话给deepcopy()
,我希望克隆与原版完全分离。但是,documentation说:
深层复制构造一个新的复合对象,然后递归地, 将副本插入到原始文件中找到的对象中。
说“将副本插入其中”代替“将深层副本插入其中”令人困惑,尤其是重点。
答案 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)
的第三方库。