轉(zhuǎn)自 Python 之 new() 方法與實例化
__new__()
是在新式類中新出現(xiàn)的方法垂攘,它作用在構(gòu)造方法建造實例之前人弓,可以這么理解,在 Python 中存在于類里面的構(gòu)造方法 __init__()
負責(zé)將類的實例化,而在 __init__()
啟動之前待锈,__new__()
決定是否要使用該__init__()
方法湖员,因為__new__()
可以調(diào)用其他類的構(gòu)造方法或者直接返回別的對象來作為本類的實例贫悄。
如果將類比喻為工廠,那么__init__()
方法則是該工廠的生產(chǎn)工人娘摔,__init__()
方法接受的初始化參數(shù)則是生產(chǎn)所需原料窄坦,__init__()
方法會按照方法中的語句負責(zé)將原料加工成實例以供工廠出貨。而__new__()
則是生產(chǎn)部經(jīng)理凳寺,__new__()
方法可以決定是否將原料提供給該生產(chǎn)部工人鸭津,同時它還決定著出貨產(chǎn)品是否為該生產(chǎn)部的產(chǎn)品,因為這名經(jīng)理可以借該工廠的名義向客戶出售完全不是該工廠的產(chǎn)品肠缨。
__new__
方法的特性:
-
__new__()
方法是在類準備將自身實例化時調(diào)用; -
__new__()
方法始終都是類的靜態(tài)方法逆趋,即使沒有被加上靜態(tài)方法裝飾器
一般類的寫法和實例化如下:
class MyClass(object):
def __init__(self, *args, **kwargs):
...
# 實例化
myclass = MyClass(*args, **kwargs)
正如以上所示,一個類可以有多個位置參數(shù)和多個命名參數(shù)晒奕,而在實例化開始時闻书,在調(diào)用 __init__()
方法之前,Python 首先調(diào)用 __new__()
方法:
def __new__(cls, *args, **kwargs):
...
第一個參數(shù)cls是當(dāng)前正在實例化的類脑慧。如果要得到當(dāng)前類的實例魄眉,應(yīng)當(dāng)在當(dāng)前類中的 __new__()
方法語句中調(diào)用當(dāng)前類的父類的 __new__()
方法。
例如闷袒,如果當(dāng)前類是直接繼承自 object坑律,那當(dāng)前類的 __new__()
方法返回的對象應(yīng)該為:
def __new__(cls, *args, **kwargs):
...
return object.__new__(cls)
注意:
事實上如果(新式)類中沒有重寫__new__()
方法,即在定義新式類時沒有重新定義__new__()
時囊骤,Python默認是調(diào)用該類的直接父類的__new__()
方法來構(gòu)造該類的實例晃择,如果該類的父類也沒有重寫__new__()
冀值,那么將一直按此規(guī)矩追溯至object的__new__()
方法,因為object是所有新式類的基類宫屠。
而如果新式類中重寫了__new__()
方法池摧,那么你可以自由選擇任意一個的其他的新式類(必定要是新式類,只有新式類必定都有__new__()
激况,因為所有新式類都是object的后代作彤,而經(jīng)典類則沒有__new__()
方法)的__new__()
方法來制造實例,包括這個新式類的所有前代類和后代類乌逐,只要它們不會造成遞歸死循環(huán)竭讳。具體看以下代碼解釋:
class Foo(object):
def __init__(self, *args, **kwargs):
...
def __new__(cls, *args, **kwargs):
return object.__new__(cls, *args, **kwargs)
# 以上return等同于
# return object.__new__(Foo, *args, **kwargs)
# return Stranger.__new__(cls, *args, **kwargs)
# return Child.__new__(cls, *args, **kwargs)
class Child(Foo):
def __new__(cls, *args, **kwargs):
return object.__new__(cls, *args, **kwargs)
# 如果Child中沒有定義__new__()方法,那么會自動調(diào)用其父類的__new__()方法來制造實例,即 Foo.__new__(cls, *args, **kwargs)。
# 在任何新式類的__new__()方法腻贰,不能調(diào)用自身的__new__()來制造實例,因為這會造成死循環(huán)胰舆。因此必須避免類似以下的寫法:
# 在Foo中避免:return Foo.__new__(cls, *args, **kwargs)或return cls.__new__(cls, *args, **kwargs)。Child同理蹬挤。
# 使用object或者沒有血緣關(guān)系的新式類的__new__()是安全的缚窿,但是如果是在有繼承關(guān)系的兩個類之間,應(yīng)避免互調(diào)造成死循環(huán)焰扳,例如:(Foo)return Child.__new__(cls), (Child)return Foo.__new__(cls)倦零。
class Stranger(object):
...
# 在制造Stranger實例時,會自動調(diào)用 object.__new__(cls)
通常來說吨悍,新式類開始實例化時扫茅,__new__()
方法會返回cls(cls指代當(dāng)前類)的實例,然后該類的__init__()
方法作為構(gòu)造方法會接收這個實例(即self)作為自己的第一個參數(shù)育瓜,然后依次傳入__new__()
方法中接收的位置參數(shù)和命名參數(shù)葫隙。
注意:如果__new__()
沒有返回cls(即當(dāng)前類)的實例,那么當(dāng)前類的__init__()
方法是不會被調(diào)用的躏仇。如果__new__()
返回其他類(新式類或經(jīng)典類均可)的實例恋脚,那么只會調(diào)用被返回的那個類的構(gòu)造方法。
class Foo(object):
def __init__(self, *args, **kwargs):
...
def __new__(cls, *args, **kwargs):
return object.__new__(Stranger, *args, **kwargs)
class Stranger(object):
...
foo = Foo()
print type(foo)
# 打印的結(jié)果顯示foo其實是Stranger類的實例钙态。
# 因此可以這么描述__new__()和__ini__()的區(qū)別慧起,在新式類中__new__()才是真正的實例化方法,
# 為類提供外殼制造出實例框架册倒,然后調(diào)用該框架內(nèi)的構(gòu)造方法__init__()使其豐滿。
# 如果以建房子做比喻磺送,__new__()方法負責(zé)開發(fā)地皮驻子,打下地基灿意,并將原料存放在工地。
# 而__init__()方法負責(zé)從工地取材料建造出地皮開發(fā)招標書中規(guī)定的大樓崇呵,__init__()負責(zé)大樓的細節(jié)設(shè)計缤剧,建造,裝修使其可交付給客戶域慷。
單例模式
最后荒辕,讓我們使用__new__()
方法來實現(xiàn)Python中的單例模式。
class OneOnly(object):
_singleton = None
def __new__(cls, *args, **kwargs):
if not cls._singleton:
cls._singleton = super(OneOnly, cls).__new__(cls, *args, **kwargs)
# 上面構(gòu)建類實例的代碼也可以更簡單的使用下面的代碼實現(xiàn)
# cls._singleton = object.__new__(cls, *args, **kwargs)
return cls._singleton
#實例化類
a1 = OneOnly()
a2 = OneOnly()
a1
a2
#輸出:(可以看到這兩個實例對象的地址是一樣的說明是一個實例對象)
<__main__.OneOnly at 0x14c6e043dd8>
<__main__.OneOnly at 0x14c6e043dd8>