Python Cookbook-6.9 快速复制对象
任务
为了使用 copy.copy,需要实现特殊方法__copy__。而且你的类的__init__比较耗时所以你希望能够绕过它并获得一个“空的”未初始化的类实例。
解决方案
下面的解决方案可同时适用于新风格和经典类:
def empty_copy(obj):class Empty(obj.__class__):def __init__(self):passnewcopy = Empty()newcopy.__class__ = obj.__class__return newcopy
你的类可以使用这个函数来实现__copy__:
class YourClass(object):def __init__(self):assume there's a lot of work heredef __copy__(self):newcopy = empty_copy(self)copy some relevant subset of self's attributes to newcopyreturn newcopy
下面是用法示例:
if __name__ == '__main__':import copyy = YourClass()#很显然,__init__会被调用print z = copy.copy(y)y#…不调用__init__print z
讨论
如同 4.1节的讨论,当你进行赋值操作时,Python 并不会暗中对对象进行复制操作,这是一个很好的处理,因为这样的机制更快、更灵活,而且也具有语义的一致性。当需要复制时,要明确地提出请求,通常使用 copy.copy 函数,它知道怎样复制内建的类型,对于自定义的对象也具有默认的行为方式,如果你想定制这个复制过程,可以定义类的__copy__特殊方法。如果你的类实例是不允许复制的,可以定义一个__copy__并抛出一个 TypeError 异常。大多数情况下,你只需要让 copy.copy 按照默认的机制工作就可以很轻松地获得克隆能力。这确实是很棒的设计,其他很多语言会强迫你实现个特殊的 clone 方法来完成实例克隆。
如果__init__是一个耗时的操作,copy__常常需要以一个“空”实例开始,从而绕开__init。最简单和通用的方法是使用Python 提供的直接修改实例的类的能力:在本地的空类中创建一个新对象,然后设置此对象的class属性,如代码所示。对旧风格(经典)类而言,从obj.class__继承 Empty 类显得比较多余(但也没什么害处)但继承机制使得本节的方案对于各种对象包括经典的和新风格的类(包括内建的和扩展类型),都具有很好的兼容性。一旦你选择从ob的类继承,必须重写 Empty 类中的__init,否则就无法实现本节方案的初衷。重写,意味着obj类的__init__不会被执行,这是因为在 Python 中,父类的初始化方法并不会自动执行。
一旦得到了一个符合要求的类的“空”对象,就需要复制 self 属性的一个子集。当需要所有属性时,最好就不要自定义__copy__,因为copy.copy 的默认行为就是复制实例的所有属性。除非你除了复制所有属性之外还想做更多的工作,在本例中,下面两种复制所有属性的方式都是可行的:
newcopy.__dict__.update(self.__dict__)
newcopy.__dict__ = dict(self.__dict__)
新风格类的实例并不一定会在__dict__中保存它的所有状态,所以你可能需要复制一些与类相关特定的状态。
基于标准模块 new 的方式对于经典类和新风格类无法做到完全透明,而且new静态方法也不能生成一个空的实例——后者只在新风格类中存在。不过,本节的方案能够解决这些问题。
实现__copy__的一种好的替代方式是实现__getstate__和__setstate__方法:这些特殊方法以明确的和原生的方式定义了你的对象的状态,绕过了__init__。另外,它们也支持类实例的序列化:见7.4节中关于这些方法的更多信息。
到此为止我们讨论的是浅拷贝,这也是你大多数时候需要的复制方式。使用浅拷贝时虽然你的对象被复制了,但是它指向的很多对象(内部的属性或者子项)却并未被复制,所以新复制的对象和原对象指向相同的属性和子项–这是一种轻量级的快速操作。而深拷贝则是一种重量级的深度操作,它根据对象的指向图谱逐一地完成对所有对象的拷贝。可以调用 copy.deepcopy 完成对一个对象的深拷贝。如果你想定制你的类实例的深拷贝方式,需要定义一个特殊方法__deepcopy__:
class YourClass(object):'''def __deepcopy__(self,memo):newcopy = empty_copy(self)#使用copy.deepcopy(self.x,memo)来获得self的相关属性#元素的子集的深拷贝,并放入newcopy中。return newcopy
如果决定要实现__deepcopy__,记住要遵守Python 标准库模块 copy 的文档定义的memoization 协议——应该复制有第二个参数的 copy.deepcopy要求的全部属性和子项同一个memo字典,还会被传递给__deepcopy__方法。另外,实现__getstate__和__setstate__也是很好的选择,因为这些方法同样支持深拷贝:Python 会处理并复制那些由__getstate__ 返回的“状态”对象,然后传递给一个新的,空实例的__setstate__方法。参看7.4 节关于这些特殊方法的更多内容。