任務
為了使用 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 節關于這些特殊方法的更多內容。