Python __new__ 和 __init__ 的區別

AragornJIA發表於2019-02-18

__new__() 是在新式類中新出現的方法,它作用在構造方法( __init__() )建造例項之前. 可以這麼理解,在 Python 中存在於類裡面的構造方法 __init__() 負責將類進行例項化,而在 __init__() 啟動之前,__new__() 決定是否要使用該 __init__() 方法,因為 __new__() 也可以呼叫其他類的構造方法或者直接返回別的物件來作為本類的例項。

如果將類比喻為工廠,那麼 __init__() 方法則是該工廠的生產工人,__init__() 方法接受的初始化引數則是生產所需原料,__init__() 方法會按照方法中的語句負責將原料加工成例項以供工廠出貨。而 __new__() 則是生產部經理,__new__() 方法可以決定是否將原料提供給該生產部工人,同時它還決定著出貨產品是否為該生產部的產品,因為這名經理可以借該工廠的名義向客戶出售完全不是該工廠的產品。

__new__() 方法的特性:

  • __new__() 方法是在類準備將自身例項化時呼叫。
  • __new__() 方法始終都是類的靜態方法,即使沒有被加上靜態方法裝飾器。

類的例項化和構造方法通常是這個樣子:

class MyClass(object):
	def __new__(cls, *args, **kwargs):
    	return object.__new__(cls, *args, **kwargs)
    def __init__(self, *args, **kwargs):
        ...
            
# 例項化
myclass = MyClass(*args, **kwargs)

正如以上程式碼所示,一個類可以有多個位置引數和多個命名引數,而在例項化開始之後,在呼叫 __init__() 方法之前,Python 首先呼叫 __new__() 方法.

__new__ 的引數解釋:

  • cls 表示當前類.
  • *args**kwargs 分別表示該類進行初始化時, 輸入的位置引數和命名引數。
    __new__ 函式一般會有返回語句, 在返回語句中: object.__new__() 表示呼叫 object 類的 __new__() 函式, 也可以使用 super().__new__(), 表示呼叫當前類的父類的 __new__(), 如果父類沒有自定義 __new__(), 就會呼叫該父類的父類的 __new__(), 以此類推, 直到 object 類. return 中的 __new__() 函式的第一個引數 cls 表示將要返回的類的型別, cls 表示用於返回當前類, 也可以返回其他類.

事實上如果(新式)類中沒有重寫 __new__() 方法,即在定義新式類時沒有重新定義 __new__() 時,Python 預設是呼叫該類的直接父類的 __new__() 方法來構造該類的例項,如果該類的父類也沒有重寫 __new__(),那麼將一直按此規則追溯至 object 類的 __new__() 方法,因為 object 類是所有新式類的基類。

如果新式類中重寫了 __new__() 方法,那麼你可以自由選擇任意一個的其他的新式類(必定要是新式類,只有新式類必定都有 __new__(),因為所有新式類都是 object 的後代,而經典類則沒有 __new__() 方法)的 __new__() 方法來製造例項,包括這個新式類的所有前代類和後代類,只要它們不會造成遞迴死迴圈。具體看以下程式碼解釋:

class Foo(object):
    def __init__(self, *args, **kwargs):
        ...
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls, *args, **kwargs)    

class Child(Foo):
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)
        
class Stranger(object):
	def __new__(cls, *args, **kwargs):
		...
  • 如果Child中沒有定義 __new__() 方法,那麼會自動呼叫其父類的 __new__() 方法來製造例項,即 Foo.__new__(cls, *args, **kwargs)
  • 在任何新式類的 __new__() 方法,不能呼叫自身的 __new__() 來製造例項,因為這會造成死迴圈。因此必須避免類似以下的寫法:
  1. Foo 中避免:return Foo.__new__(cls, *args, **kwargs)return cls.__new__(cls, *args, **kwargs)Child 同理。
  2. 使用 object 或者沒有血緣關係的新式類的 __new__() 是安全的,但是如果是在有繼承關係的兩個類之間,應避免互調造成死迴圈,例如: (Foo) return Child.__new__(cls) , (Child) return Foo.__new__(cls)
  • 在製造 Stranger 例項時,會自動呼叫 object.__new__(cls).

通常來說,新式類開始例項化時,__new__() 方法會返回 clscls 指代當前類)的例項,然後該類的 __init__() 方法作為構造方法會接收這個例項(即self)作為自己的第一個引數,然後依次傳入 __new__() 方法中接收的位置引數和命名引數。

__new__() 除了返回 cls (當前類) 的例項之外,還可以有其他用法:

  • 返回其他類:
class Foo(object):
    def __new__(cls, *args, **kwargs):
        return object.__new__(Stranger, *args, **kwargs) 
        
    def __init__(self, *args, **kwargs):
        ... 

class Stranger(object):
    ...

foo = Foo()
print(type(foo))    

列印的結果顯示 foo 其實是 Stranger 類的例項。

  • 返回其他資料型別:
a = 10

class Foo(object):
    def __new__(cls, *args, **kwargs):
        print("__new__ is called.")
        return a
    def __init__(self, *args, **kwargs):
        ...
Foo()

執行結果為:

__new__ is called
10

可以這麼描述 __new__()__ini__() 的區別,在新式類中 __new__() 才是真正的例項化方法,為類提供外殼製造出例項框架,然後呼叫該框架內的構造方法 __init__() 使其豐滿。

如果以建房子做比喻,__new__() 方法負責開發地皮,打下地基,並將原料存放在工地。而 __init__() 方法負責從工地取材料建造出地皮開發招標書中規定的大樓,__init__() 負責大樓的細節設計,建造,裝修使其可交付給客戶。

注意:如果 __new__() 沒有返回 cls(即當前類)的例項,那麼當前類的 __init__() 方法是不會被呼叫的。如果 __new__() 返回其他類(新式類或經典類均可)的例項,那麼只會呼叫被返回的那個類的構造方法。

相關文章