真假構(gòu)造函數(shù)
如果你去面試Python工程師的崗位,面試官問(wèn)你,請(qǐng)問(wèn)Python當(dāng)中的類的構(gòu)造函數(shù)是什么?
你不假思索,當(dāng)然是__init__啦!如果你這么回答,很有可能你就和offer無(wú)緣了。因?yàn)樵赑ython當(dāng)中__init__并不是構(gòu)造函數(shù),__new__才是。是不是有點(diǎn)蒙,多西得(日語(yǔ):為什么)?我們不是一直將__init__方法當(dāng)做構(gòu)造函數(shù)來(lái)用的嗎?怎么又冒出來(lái)一個(gè)__new__,如果__new__才是構(gòu)造函數(shù),那么為什么我們創(chuàng)建類的時(shí)候從來(lái)不用它呢?
別著急,我們慢慢來(lái)看。首先我們回顧一下__init__的用法,我們隨便寫一段代碼:
classStudent:
def__init__(self,name,gender):
self.name=name
self.gender=gender
我們一直都是這么用的,對(duì)不對(duì),毫無(wú)問(wèn)題。但是我們換一個(gè)問(wèn)題,我們?cè)赑ython當(dāng)中怎么實(shí)現(xiàn)單例(Singleton)的設(shè)計(jì)模式呢?怎么樣實(shí)現(xiàn)工廠呢?
從這個(gè)問(wèn)題出發(fā),你會(huì)發(fā)現(xiàn)只使用__init__函數(shù)是不可能完成的,因?yàn)開(kāi)_init__并不是構(gòu)造函數(shù),它只是初始化方法。也就是說(shuō)在調(diào)用__init__之前,我們的實(shí)例就已經(jīng)被創(chuàng)建好了,__init__只是為這個(gè)實(shí)例賦上了一些值。如果我們把創(chuàng)建實(shí)例的過(guò)程比喻成做一個(gè)蛋糕,__init__方法并不是烘焙蛋糕的,只是點(diǎn)綴蛋糕的。那么顯然,在點(diǎn)綴之前必須先烘焙出一個(gè)蛋糕來(lái)才行,那么這個(gè)烘焙蛋糕的函數(shù)就是__new__。
__new__函數(shù)
我們來(lái)看下__new__這個(gè)函數(shù)的定義,我們?cè)谑褂肞ython面向?qū)ο蟮臅r(shí)候,一般都不會(huì)重構(gòu)這個(gè)函數(shù),而是使用Python提供的默認(rèn)構(gòu)造函數(shù),Python默認(rèn)構(gòu)造函數(shù)的邏輯大概是這樣的:
def__new__(cls,*args,**kwargs):
returnsuper().__new__(cls,*args,**kwargs)
從代碼可以看得出來(lái),函數(shù)當(dāng)中基本上什么也沒(méi)做,就原封不動(dòng)地調(diào)用了父類的構(gòu)造函數(shù)。這里隱藏著Python當(dāng)中類的創(chuàng)建邏輯,是根據(jù)繼承關(guān)系一級(jí)一級(jí)創(chuàng)建的。根據(jù)邏輯關(guān)系,我們可以知道,當(dāng)我們創(chuàng)建一個(gè)實(shí)例的時(shí)候,實(shí)際上是先調(diào)用的__new__函數(shù)創(chuàng)建實(shí)例,然后再調(diào)用__init__對(duì)實(shí)例進(jìn)行的初始化。我們可以簡(jiǎn)單做個(gè)實(shí)驗(yàn):
classTest:
def__new__(cls):
print('__new__')
returnobject().__new__(cls)
def__init__(self):
print('__init__')
當(dāng)我們創(chuàng)建Test這個(gè)類的時(shí)候,通過(guò)輸出的順序就可以知道Python內(nèi)部的調(diào)用順序。
從結(jié)果上來(lái)看,和我們的推測(cè)完全一樣。
單例模式
那么我們重載__new__函數(shù)可以做什么呢?一般都是用來(lái)完成__init__無(wú)法完成的事情,比如前面說(shuō)的單例模式,通過(guò)__new__函數(shù)就可以實(shí)現(xiàn)。我們來(lái)簡(jiǎn)單實(shí)現(xiàn)一下:
classSingletonObject:
def__new__(cls,*args,**kwargs):
ifnothasattr(SingletonObject,"_instance"):
SingletonObject._instance=object.__new__(cls)
returnSingletonObject._instance
def__init__(self):
pass
當(dāng)然,如果是在并發(fā)場(chǎng)景當(dāng)中使用,還需要加上線程鎖防止并發(fā)問(wèn)題,但邏輯是一樣的。
除了可以實(shí)現(xiàn)一些功能之外,還可以控制實(shí)例的創(chuàng)建。因?yàn)镻ython當(dāng)中是先調(diào)用的__new__再調(diào)用的__init__,所以如果當(dāng)調(diào)用__new__的時(shí)候返回了None,那么最后得到的結(jié)果也是None。通過(guò)這個(gè)特性,我們可以控制類的創(chuàng)建。比如設(shè)置條件,只有在滿足條件的時(shí)候才能正確創(chuàng)建實(shí)例,否則會(huì)返回一個(gè)None。
比如我們想要?jiǎng)?chuàng)建一個(gè)類,它是一個(gè)int,但是不能為0值,我們就可以利用__new__的這個(gè)特性來(lái)實(shí)現(xiàn):
classNonZero(int):
def__new__(cls,value):
returnsuper().__new__(cls,value)ifvalue!=0elseNone
那么當(dāng)我們用0值來(lái)創(chuàng)建它的時(shí)候就會(huì)得到一個(gè)None,而不是一個(gè)實(shí)例。
工廠模式
理解了__new__函數(shù)的特性之后,我們就可以靈活運(yùn)用了。我們可以用它來(lái)實(shí)現(xiàn)許多其他的設(shè)計(jì)模式,比如大名鼎鼎經(jīng)常使用的工廠模式。
所謂的工廠模式是指通過(guò)一個(gè)接口,根據(jù)參數(shù)的取值來(lái)創(chuàng)建不同的實(shí)例。創(chuàng)建過(guò)程的邏輯對(duì)外封閉,用戶不必關(guān)系實(shí)現(xiàn)的邏輯。就好比一個(gè)工廠可以生產(chǎn)多種零件,用戶并不關(guān)心生產(chǎn)的過(guò)程,只需要告知需要零件的種類。也因此稱為工廠模式。
比如說(shuō)我們來(lái)創(chuàng)建一系列游戲的類:
classLast_of_us:
defplay(self):
print('theLastOfUsisreallyfunny')
classUncharted:
defplay(self):
print('theUnchartedisreallyfunny')
classPSGame:
defplay(self):
print('PShasmanygames')
然后這個(gè)時(shí)候我們希望可以通過(guò)一個(gè)接口根據(jù)參數(shù)的不同返回不同的游戲,如果不通過(guò)__new__,這段邏輯就只能寫成函數(shù)而不能通過(guò)面向?qū)ο髞?lái)實(shí)現(xiàn)。通過(guò)重載__new__我們就可以很方便地用參數(shù)來(lái)獲取不同類的實(shí)例:
classGameFactory:
games={'last_of_us':Last_Of_us,'uncharted':Uncharted}
def__new__(cls,name):
ifnameincls.games:
returncls.games[name]()
else:
returnPSGame()
uncharted=GameFactory('uncharted')
last_of_us=GameFactory('last_of_us')
總結(jié)
相信看到這里,關(guān)于__new__這個(gè)函數(shù)的用法應(yīng)該都能理解了。一般情況下我們是用不到這個(gè)函數(shù)的,只會(huì)在一些特殊的場(chǎng)景下使用。雖然如此,我們學(xué)會(huì)它并不只是用來(lái)實(shí)現(xiàn)設(shè)計(jì)模式,更重要的是可以加深我們對(duì)于Python面向?qū)ο蟮睦斫狻?/p>
以上內(nèi)容為大家介紹了Python之__init__和__new__的區(qū)別是什么,希望對(duì)大家有所幫助,如果想要了解更多Python相關(guān)知識(shí),請(qǐng)關(guān)注IT培訓(xùn)機(jī)構(gòu):千鋒教育。