2011-02-01 49 views
93

我現在要問的問題似乎是Python's use of __new__ and __init__?的重複,但無論如何,我現在還不清楚__new____init__之間的實際區別是什麼。Python(和Python C API):__new__與__init__

在您急於告訴我__new__用於創建對象並且__init__用於初始化對象之前,請讓我清楚:我明白了。事實上,這種區分對我來說很自然,因爲我有C++的經驗,我們有placement new,它類似地將對象分配與初始化分開。

Python C API tutorial解釋這樣的:

新成員負責創建 (相對於初始化)的類型的 對象。它作爲__new__()方法在 Python中公開。 ... 實施新方法的一個原因是確保 實例變量的初始值。

所以,是的 - 我得到做什麼__new__,但儘管這樣,我還是不明白爲什麼它在Python是非常有用的。給出的例子說如果你想「確保實例變量的初始值」,__new__可能是有用的。那麼,是不是__init__會做什麼?

在C API教程中,顯示​​了一個示例,其中創建了一個新類型(稱爲「Noddy」),並定義了類型的__new__函數。諾迪類型包含稱爲first串部件,和這個字符串成員被初始化爲空字符串,像這樣:

static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) 
{ 
    ..... 

    self->first = PyString_FromString(""); 
    if (self->first == NULL) 
    { 
     Py_DECREF(self); 
     return NULL; 
    } 

    ..... 
} 

注意,如果沒有這裏定義的__new__方法,我們不得不使用PyType_GenericNew,它只是初始化所有的實例變量成員都爲NULL。所以__new__方法的唯一好處是實例變量將以空字符串開始,而不是NULL。 但是爲什麼這樣做會有用,因爲如果我們關心確保實例變量初始化爲某個默認值,那麼我們可以在__init__方法中做到這一點?

回答

105

不同之處主要在於可變與不可變類型。

__new__接受類型作爲第一個參數,並且(通常)返回該類型的新實例。因此它適用於可變和不可變類型。

__init__接受實例作爲第一個參數並修改該實例的屬性。這對於不可變類型是不合適的,因爲它允許在創建後通過調用obj.__init__(*args)來修改它們。

比較的tuplelist行爲:

>>> x = (1, 2) 
>>> x 
(1, 2) 
>>> x.__init__([3, 4]) 
>>> x # tuple.__init__ does nothing 
(1, 2) 
>>> y = [1, 2] 
>>> y 
[1, 2] 
>>> y.__init__([3, 4]) 
>>> y # list.__init__ reinitialises the object 
[3, 4] 

至於爲什麼他們是獨立的(除了簡單的歷史原因):__new__方法需要一堆樣板得到正確(初始對象創建,然後記住最後返回對象)。相比之下,__init__方法非常簡單,因爲您只需設置需要設置的任何屬性即可。

__init__方法是更容易編寫和可變VS不變區別

除了上面提到的,分離也可以利用來使調用子類在__new__設置任何絕對必要的情況下不變量可選的父類__init__。但這通常是一種可疑的做法 - 根據需要調用父類__init__方法通常會更清楚。

+1

您在`__new__`中引用爲「樣板」的代碼不是樣板,因爲樣板不會更改。有時你需要用不同的東西替換特定的代碼。 – 2013-01-23 07:34:09

+10

創建或以其他方式獲取實例(通常使用`super`調用)並返回實例是任何`__new__`實現的必要部分,以及我所指的「樣板」。相比之下,`pass`是`__init__`的有效實現 - 不需要任何行爲。 – ncoghlan 2013-01-24 10:08:28

24

__new__()可以返回與其綁定類別不同的​​對象。 __init__()只初始化該類的現有實例。

>>> class C(object): 
... def __new__(cls): 
...  return 5 
... 
>>> c = C() 
>>> print type(c) 
<type 'int'> 
>>> print c 
5 
+0

這是迄今爲止最精簡的解釋。 – Tarik 2011-11-12 18:05:27

+0

不完全正確。我有`__init__`方法,其中包含類似`self .__ class__ = type(...)`的代碼。這會導致該對象與您以前創建的對象不同。我不能像你所做的那樣把它變成一個`int` ......我得到關於堆類型的錯誤......但是我將它分配給一個動態創建的類的例子起作用。 – ArtOfWarfare 2016-01-21 18:52:21

31

__new__可能還有其他用途,但有一個非常明顯的用途:您不能在不使用__new__的情況下繼承不可變類型。例如,假設你想創建一個元組的子類,它只能包含0到size之間的整數值。

class ModularTuple(tuple): 
    def __new__(cls, tup, size=100): 
     tup = (int(x) % size for x in tup) 
     return super(ModularTuple, cls).__new__(cls, tup) 

你根本無法與__init__做到這一點 - 如果你試圖修改self__init__,解釋器會抱怨你試圖修改不可變對象。

11

不是一個完整的答案,但也許是說明差異的東西。

__new__總是會在需要創建對象時調用。有些情況下__init__不會被調用。一個例子是,當您從pickle文件中取消對象時,它們將被分配(__new__)但未初始化(__init__)。

0

只是想添加一個關於定義__new____init__意圖(與行爲相反)的字。

當我試圖理解定義類工廠的最佳方法時,我遇到了這個問題(除其他外)。我意識到,在__new____init__概念上不同的方法之一是事實,__new__的好處是什麼的問題指出:

所以方法的唯一好處是,實例變量將以空字符串開始,而不是NULL。但是,爲什麼這會很有用,因爲如果我們關心確保實例變量初始化爲某個默認值,那麼我們可以在init方法中做到這一點?

考慮所陳述的情況下,我們關注的實例變量的初始值,當實例在現實中是一類本身。因此,如果我們在運行時動態地創建一個類對象,並且我們需要定義/控制創建該類的後續實例的特殊事項,我們將在元類的__new__方法中定義這些條件/屬性。

我對此感到困惑,直到我真正考慮了這個概念的應用,而不僅僅是它的意義。這裏有一個例子,將有望明確的區別:

a = Shape(sides=3, base=2, height=12) 
b = Shape(sides=4, length=2) 
print(a.area()) 
print(b.area()) 

# I want `a` and `b` to be an instances of either of 'Square' or 'Triangle' 
# depending on number of sides and also the `.area()` method to do the right 
# thing. How do I do that without creating a Shape class with all the 
# methods having a bunch of `if`s ? Here is one possibility 

class Shape: 
    def __new__(cls, sides, *args, **kwargs): 
     if sides == 3: 
      return Triangle(*args, **kwargs) 
     else: 
      return Square(*args, **kwargs) 

class Triangle: 
    def __init__(self, base, height): 
     self.base = base 
     self.height = height 

    def area(self): 
     return (self.base * self.height)/2 

class Square: 
    def __init__(self, length): 
     self.length = length 

    def area(self): 
     return self.length*self.length 

注意這只是一個demonstartive例子。有很多方法可以在不採用類似上述的類工廠方法的情況下獲得解決方案,即使我們選擇以這種方式實現解決方案,爲了簡潔也省略了一些警告(例如,明確聲明元類)

如果您正在創建一個常規類(又名非元類),那麼__new__沒有什麼意義,除非它是ncoghlan's answer答案中的可變與不可變情形的特殊情況(這實質上是一個更具體的例子定義通過__new__創建的類/類型的初始值/屬性的概念,然後通過__init__進行初始化)。