2011-12-26 29 views
8

我想了解在創建過程可以通過構造函數或通過__new__方法創建Python類的新實例時應如何創建。特別是,我注意到在使用構造函數時,將在__new__之後自動調用__init__方法,而直接調用__new__時,不會自動調用__init__類。當通過在__new__內嵌入對__init__的調用來顯式調用__new__時,我可以強制調用__init__,但當通過構造函數創建類時,最終會調用__init__兩次。確保__init__只在構造函數創建類實例時調用一次或__new__

例如,考慮以下玩具類,它存儲一個內部屬性,即list對象data:將此視爲矢量類的開始很有用。

class MyClass(object): 
    def __new__(cls, *args, **kwargs): 
     obj = object.__new__(cls, *args, **kwargs) 
     obj.__init__(*args, **kwargs) 
     return obj 

    def __init__(self, data): 
     self.data = data 

    def __getitem__(self, index): 
     return self.__new__(type(self), self.data[index]) 

    def __repr__(self): 
     return repr(self.data) 

類的新實例可以使用構造函數來創建或者(實際上並不知道這是在Python正確的術語),像

x = MyClass(range(10))

或通過切片,其你可以看到在__getitem__方法中調用__new__

x2 = x[0:2]

在第一種情況下,__init__會被調用兩次(都是通過內__new__顯式調用,然後再自動),一旦在第二個實例。很明顯,我只想在任何情況下都會調用一次__init__。有沒有一種標準的方法來在Python中做到這一點?

注意,在我的例子,我可以擺脫__new__方法,並重新定義__getitem__作爲

def __getitem__(self, index): 
    return MyClass(self.data[index]) 

,但那麼這將導致一個問題,如果我以後想從MyClass繼承,因爲如果我撥打電話像child_instance[0:2]我會找回MyClass的一個實例,而不是子類。

回答

8

首先,關於__new____init__一些基本事實:

  • __new__是a 構造函數
  • __new__通常會返回cls的實例,它的第一個參數。
  • __new__返回cls,__new__ causes Python to call __init__的實例。
  • __init__初始值設定項。它修改由__new__返回的實例(self) 。它不需要返回self

MyClass定義:

def __new__(cls, *args, **kwargs): 
    obj = object.__new__(cls, *args, **kwargs) 
    obj.__init__(*args, **kwargs) 
    return obj 

MyClass.__init__被調用兩次。一旦明確地致電obj.__init__,並且第二次因爲__new__返回obj,則cls的實例。 (由於第一個參數object.__new__cls,返回的實例是MyClass所以obj.__init__電話MyClass.__init__,不object.__init__一個實例。)


Python 2.2.3 release notes有一個有趣的評論,它在何時使用__new__雞舍光何時使用__init__

__new__方法是用類作爲第一個參數調用的;其 的責任是返回該類的新實例。

將此與__init__進行比較:__init__以實例作爲其第一個參數調用 ,並且它不返回任何內容;其責任是 初始化實例。

所有這些都是爲了讓不可變類型在保留子類的同時可以保持它們的不變性。

不可改變類型(int,長,浮動的,複雜的,STR,Unicode和 元組)有一個虛擬__init__,而可變類型(字典,列表, 文件,也超強,類方法,靜態方法,和物業)有一個 假人__new__

因此,請使用__new__來定義不可變類型,並使用__init__來定義可變類型。雖然可以定義兩者,但不應該這樣做。


因此,由於MyClass的是可變的,你應該只定義__init__:當你創建一個類的實例與MyClass(args)

class MyClass(object): 
    def __init__(self, data): 
     self.data = data 

    def __getitem__(self, index): 
     return type(self)(self.data[index]) 

    def __repr__(self): 
     return repr(self.data) 

x = MyClass(range(10)) 
x2 = x[0:2] 
+0

感謝@unutbu的詳細介紹。 – Abiel

+1

這是誤導。首先'__new__'不會調用'__init__'然後返回實例。調用'__new__'來創建一個實例,然後調用'__init__'來返回'__new__'。這就是爲什麼直接調用__new__不會調用__init__。其次,'__new__'不是一個構造函數,不應該被稱爲一個。 '__init__'在官方Python文檔中被稱爲構造函數。 – Ben

+0

@Ben,你有沒有從Python文檔的參考?看起來這將清理很多關於'__init__'是否是構造函數的爭論... https://stackoverflow.com/questions/6578487/init-as-a-constructor – pylang

1

有一對夫婦的事情,不應該做的:

  • 呼叫__init____new__
  • 呼叫__new__直接的方法

正如你已經看到,無論是在創建給定類的對象時,會自動調用__new____init__方法。直接使用它們會破壞這個功能(在另一個__init__內允許調用__init__,雖然可以在下面的例子中看到)。

您可以獲取類對象的任何方法獲得__class__屬性,如下面的例子:

class MyClass(object): 
    def __new__(cls, *args, **kwargs): 
     # Customized __new__ implementation here 
     return obj 

    def __init__(self, data): 
     super(MyClass, self).__init__(self) 
     self.data = data 

    def __getitem__(self, index): 
     cls = self.__class__ 
     return cls(self.data[index]) 

    def __repr__(self): 
     return repr(self.data) 

x = MyClass(range(10)) 
x2 = x[0:2] 
+0

然後在你的例子中顯示類中__new __()的用途是什麼? – martineau

+0

@martineau感謝您的評論。示例中的__new__'實現就像任何期望實現的佔位符一樣。無論如何,正如@unutbu在他的回覆中指出的那樣,對於這個特定問題確實沒有必要,所以我已經更新了我的回覆以避免混淆。 – jcollado

0

,默認實例創建順序如下:

  1. MyClass.__new__(args)被調用以獲得新的「空白」實例
  2. new_instance.__init__(args)被調用(new_instance是從呼叫到如上__new__返回的實例)來初始化新的實例[1]
  3. new_instance作爲返回的MyClass(args)

從這個結果的屬性,它是清楚地看到自己調用MyClass.__new__而不是導致__init__被調用,所以你最終會得到一個未初始化的實例。同樣清楚的是,撥打電話__init____new__也將是不正確的,因爲MyClass(args)將撥打__init__兩次

你的問題的來源是這樣的:

我想了解Python類的如何利用新的實例應該 創建的時候創建過程既可以通過通過構造或 是新方法

創建過程通常不應該通過__new__方法。 __new__是正常實例創建協議的部分,所以您不應期望它爲您調用整個協議。

一個(壞的)解決方案是自己實現這個協議;而不是:

def __getitem__(self, index): 
    return self.__new__(type(self), self.data[index]) 

你可以有:

def __getitem__(self, index): 
    new_item = self.__new__(type(self), self.data[index]) 
    new_item.__init__(self.data[index]) 
    return new_item 

不過說真的,你想要做什麼也不是好惹__new__可言。默認情況下__new__適合您的情況,並且默認實例創建協議適用於您的情況,因此您既不應執行__new__也不應直接調用它。

你想要的是通過調用類來以正常的方式創建類的新實例。如果沒有遺傳繼續發生,並且您認爲永遠不會發生,只需將self.__new__(type(self), self.data[index])替換爲MyClass(self.data[index])即可。

如果你認爲有一天可能是MyClass的子類,它想要通過切片創建子類的實例而不是MyClass,那麼你需要動態獲取類self並調用它。你已經知道如何做到這一點,因爲你在你的程序中使用它!type(self)將返回self的類型(類),然後您可以直接調用它,直接調用MyClasstype(self)(self.data[index])


順便說一句,的__new__的一點是,當你想定製它被初始化之前得到一個類的「新」的空白實例的過程。幾乎所有的時間,這是完全沒有必要的,默認__new__是好的。

你只需要__new__在兩種情況下:

  1. 你有一個不尋常的「分配」方案,您可能會返回一個現有的實例,而不是創建一個真正的新的(實際創建的唯一途徑無論如何,一個新實例是委託給最終的默認實現__new__)。
  2. 你正在實現一個不可變的內建類型的子類。由於不可修改的內建類型在創建後無法修改(因爲它們是不可變的),所以它們必須初始化爲,因爲它們是在之間創建的,而不是在__init__之後創建的。

由於點的概括(1),可以使__new__回報任何你喜歡的(不一定是類的實例),使一些任意離奇的方式調用類的行爲。不過,這看起來似乎總是比有幫助的更令人困惑。 [1]我相信事實上該協議稍微複雜一些; __init__只在__new__返回的值上被調用,如果它是被調用來啓動進程的類的實例。然而,情況並非如此,這是非常罕見的。

相關問題