[英]Python (and Python C API): __new__ versus __init__
我要問的問題似乎是Python 使用 __new__ 和 __init__的重復? ,但無論如何,我仍然不清楚__new__
和__init__
之間的實際區別是什么。
在你急於告訴我__new__
用於創建對象而__init__
用於初始化對象之前,讓我說清楚:我明白了。 事實上,這種區別對我來說很自然,因為我在 C++ 中有經驗,我們有放置 new ,它類似地將對象分配與初始化分開。
Python C API 教程是這樣解釋的:
新成員負責創建(而不是初始化)該類型的對象。 它在 Python 中作為
__new__()
方法__new__()
。 ...實現新方法的一個原因是確保實例變量的初始值。
所以,是的-我得到了__new__
呢,但盡管這樣,我還是不明白為什么它在Python是非常有用的。 給出的例子說__new__
如果你想“確保實例變量的初始值”可能很有用。 那么,這不正是__init__
會做的嗎?
在 C API 教程中,顯示了一個示例,其中創建了一個新類型(稱為“Noddy”),並定義了該類型的__new__
函數。 Noddy 類型包含一個名為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__
定義__new__
方法,我們必須使用PyType_GenericNew
,它只是將所有實例變量成員初始化為 NULL。 所以__new__
方法的唯一好處是實例變量將以空字符串開始,而不是 NULL。 但是為什么這總是有用的,因為如果我們關心確保我們的實例變量被初始化為某個默認值,我們可以在__init__
方法中完成它?
差異主要出現在可變和不可變類型上。
__new__
接受一個類型作為第一個參數,並且(通常)返回該類型的一個新實例。 因此它適用於可變和不可變類型。
__init__
接受一個實例作為第一個參數並修改該實例的屬性。 這對於不可變類型是不合適的,因為它允許在創建后通過調用obj.__init__(*args)
來修改它們。
比較tuple
和list
的行為:
>>> 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__
方法更容易編寫,以及上面提到的可變與不可變的區別,通過在__new__
設置任何絕對需要的實例不變量,還可以利用這種分離來使在子類中調用父類__init__
成為可選。 不過,這通常是一種可疑的做法 - 通常只在必要時調用父類__init__
方法會更清楚。
__new__
可能還有其他用途,但有一個非常明顯的用途:您不能在不使用__new__
情況下對不可變類型進行子類__new__
。 例如,假設您想創建一個 tuple 的子類,它只能包含 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__
做到這一點——如果你試圖在__init__
修改self
,解釋器會抱怨你試圖修改一個不可變的對象。
__new__()
可以返回與其綁定的類不同的類型的對象。 __init__()
僅初始化類的現有實例。
>>> class C(object):
... def __new__(cls):
... return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5
不是一個完整的答案,但也許可以說明差異。
__new__
將始終在必須創建對象時被調用。 在某些情況下__init__
不會被調用。 一個例子是當你從一個 pickle 文件中 unpickle 對象時,它們將被分配( __new__
)但不會被初始化( __init__
)。
只想添加一個關於定義__new__
與__init__
的意圖(而不是行為)的__new__
。
當我試圖理解定義類工廠的最佳方法時,我遇到了這個問題(以及其他問題)。 我意識到,在其中的方式之一__new__
是從概念上不同的__init__
是一個事實,即受益__new__
正是在這個問題指出:
所以 __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
請注意,這只是一個演示示例。 有多種方法可以在不使用上述類工廠方法的情況下獲得解決方案,即使我們確實選擇以這種方式實現解決方案,為了簡潔起見,也有一些注意事項被省略(例如,顯式聲明元類)
如果您正在創建一個常規類(又名非元類),那么__new__
並沒有真正意義,除非它是特殊情況,例如ncoghlan 的答案答案中的可變與不可變場景(這本質上是概念的更具體示例)定義通過__new__
創建的類/類型的初始值/屬性,然后通過__init__
進行初始化)。
__new__
一個特殊用途是使類成為單例:
class SingletonClass(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(SingletonClass, cls).__new__(cls)
return cls.instance
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.