簡析 __init__、__new__鄙漏、__call__ 方法

任何事物都有一個(gè)從創(chuàng)建嗤谚,被使用,再到消亡的過程怔蚌,在程序語言面向?qū)ο缶幊棠P椭泄剑瑢?duì)象也有相似的命運(yùn):創(chuàng)建、初始化桦踊、使用椅野、垃圾回收,不同的階段由不同的方法(角色)負(fù)責(zé)執(zhí)行钞钙。

定義一個(gè)類時(shí)鳄橘,大家用得最多的就是 __init__方法,而__new____call__ 使用得比較少芒炼,這篇文章試圖幫助大家把這3個(gè)方法的正確使用方式和應(yīng)用場(chǎng)景分別解釋一下瘫怜。

關(guān)于 Python 新式類和老式類在這篇文章不做過多討論,因?yàn)槔鲜筋愂?Python2 中的概念本刽,現(xiàn)在基本沒人再會(huì)去用老式類鲸湃,新式類必須顯示地繼承 object,而 Python3 中子寓,只有新式類暗挑,默認(rèn)繼承了 object,無需顯示指定斜友,本文代碼都是基于 Python3 來討論炸裆。

__init__方法

__init__方法負(fù)責(zé)對(duì)象的初始化鲜屏,系統(tǒng)執(zhí)行該方法前,其實(shí)該對(duì)象已經(jīng)存在了,要不然初始化什么東西呢务热?先看例子:

# class A(object): python2 必須顯示地繼承object
class A:
    def __init__(self):
        print("__init__ ")
        super(A, self).__init__()

    def __new__(cls):
        print("__new__ ")
        return super(A, cls).__new__(cls)

    def __call__(self):  # 可以定義任意參數(shù)
        print('__call__ ')

A()

輸出
__new__
__init__

從輸出結(jié)果來看, new方法先被調(diào)用该镣,返回一個(gè)實(shí)例對(duì)象,接著 init 被調(diào)用嫁审。 call方法并沒有被調(diào)用赖晶,這個(gè)我們放到最后說律适,先來說說前面兩個(gè)方法,稍微改寫成:

def __init__(self):
    print("__init__ ")
    print(self)
    super(A, self).__init__()

def __new__(cls):
    print("__new__ ")
    self = super(A, cls).__new__(cls)
    print(self)
    return self
輸出:
__new__ 
<__main__.A object at 0x1007a95f8>
__init__ 
<__main__.A object at 0x1007a95f8>

從輸出結(jié)果來看遏插,__new__ 方法的返回值就是類的實(shí)例對(duì)象捂贿,這個(gè)實(shí)例對(duì)象會(huì)傳遞給__init__ 方法中定義的 self 參數(shù),以便實(shí)例對(duì)象可以被正確地初始化胳嘲。

如果 __new__ 方法不返回值(或者說返回 None)那么 __init__ 將不會(huì)得到調(diào)用,這個(gè)也說得通了牛,因?yàn)閷?shí)例對(duì)象都沒創(chuàng)建出來鹰祸,調(diào)用 init 也沒什么意義,此外蛙婴,Python 還規(guī)定粗井,__init__ 只能返回 None 值,否則報(bào)錯(cuò)滚朵,這個(gè)留給大家去試移宅。

__init__方法可以用來做一些初始化工作倔喂,比如給實(shí)例對(duì)象的狀態(tài)進(jìn)行初始化:

def __init__(self, a, b):
    self.a = a
    self.b = b
    super(A, self).__init__()

__new__ 方法

一般我們不會(huì)去重寫該方法脾拆,除非你確切知道怎么做渔期,什么時(shí)候你會(huì)去關(guān)心它呢,它作為構(gòu)造函數(shù)用于創(chuàng)建對(duì)象疯趟,是一個(gè)工廠函數(shù)拘哨,專用于生產(chǎn)實(shí)例對(duì)象。著名的設(shè)計(jì)模式之一信峻,單例模式倦青,就可以通過此方法來實(shí)現(xiàn)。在自己寫框架級(jí)的代碼時(shí)盹舞,可能你會(huì)用到它产镐,我們也可以從開源代碼中找到它的應(yīng)用場(chǎng)景,例如微型 Web 框架 Bootle 就用到了踢步。

class BaseController(object):
    _singleton = None
    def __new__(cls, *a, **k):
        if not cls._singleton:
            cls._singleton = object.__new__(cls, *a, **k)
        return cls._singleton

這段代碼出自 https://github.com/bottlepy/bottle/blob/release-0.6/bottle.py

這就是通過 __new__ 方法是實(shí)現(xiàn)單例模式的的一種方式癣亚,如果實(shí)例對(duì)象存在了就直接返回該實(shí)例即可,如果還沒有获印,那么就先創(chuàng)建一個(gè)實(shí)例述雾,再返回。當(dāng)然兼丰,實(shí)現(xiàn)單例模式的方法不只一種玻孟,Python之禪有說:

There should be one— and preferably only one —obvious way to do it.
用一種方法,最好是只有一種方法來做一件事

__call__ 方法

關(guān)于 __call__ 方法鳍征,不得不先提到一個(gè)概念取募,就是可調(diào)用對(duì)象(callable),我們平時(shí)自定義的函數(shù)蟆技、內(nèi)置函數(shù)和類都屬于可調(diào)用對(duì)象玩敏,但凡是可以把一對(duì)括號(hào)()應(yīng)用到某個(gè)對(duì)象身上都可稱之為可調(diào)用對(duì)象,判斷對(duì)象是否為可調(diào)用對(duì)象可以用函數(shù) callable

如果在類中實(shí)現(xiàn)了 __call__ 方法质礼,那么實(shí)例對(duì)象也將成為一個(gè)可調(diào)用對(duì)象旺聚,我們回到最開始的那個(gè)例子:

a = A()
print(callable(a))  # True

a是實(shí)例對(duì)象,同時(shí)還是可調(diào)用對(duì)象眶蕉,那么我就可以像函數(shù)一樣調(diào)用它砰粹。試試:

a()  # __call__

很神奇不是,實(shí)例對(duì)象也可以像函數(shù)一樣作為可調(diào)用對(duì)象來用造挽,那么碱璃,這個(gè)特點(diǎn)在什么場(chǎng)景用得上呢?這個(gè)要結(jié)合類的特性來說饭入,類可以記錄數(shù)據(jù)(屬性)嵌器,而函數(shù)不行(閉包某種意義上也可行),利用這種特性可以實(shí)現(xiàn)基于類的裝飾器谐丢,在類里面記錄狀態(tài)爽航,比如蚓让,下面這個(gè)例子用于記錄函數(shù)被調(diào)用的次數(shù):

class Counter:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)

@Counter
def foo():
    pass

for i in range(10):
    foo()

print(foo.count)  # 10

在 Bottle 中也有 call 方法 的使用案例,另外讥珍,stackoverflow 也有一些關(guān)于 call 的實(shí)踐例子历极,推薦看看,如果你的項(xiàng)目中衷佃,需要更加抽象化趟卸、框架代碼,那么這些高級(jí)特性往往能發(fā)揮出它作用氏义。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锄列,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子觅赊,更是在濱河造成了極大的恐慌,老刑警劉巖琼稻,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吮螺,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡帕翻,警方通過查閱死者的電腦和手機(jī)鸠补,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嘀掸,“玉大人紫岩,你說我怎么就攤上這事〔撬” “怎么了泉蝌?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長揩晴。 經(jīng)常有香客問我勋陪,道長,這世上最難降的妖魔是什么硫兰? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任诅愚,我火速辦了婚禮,結(jié)果婚禮上劫映,老公的妹妹穿的比我還像新娘违孝。我一直安慰自己,他們只是感情好泳赋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布雌桑。 她就那樣靜靜地躺著,像睡著了一般祖今。 火紅的嫁衣襯著肌膚如雪筹燕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音撒踪,去河邊找鬼过咬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛制妄,可吹牛的內(nèi)容都是我干的掸绞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼耕捞,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼衔掸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起俺抽,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤敞映,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后磷斧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體振愿,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年弛饭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冕末。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侣颂,死狀恐怖档桃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情憔晒,我是刑警寧澤藻肄,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站拒担,受9級(jí)特大地震影響仅炊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜澎蛛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一抚垄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谋逻,春花似錦呆馁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至气堕,卻和暖如春纺腊,著一層夾襖步出監(jiān)牢的瞬間畔咧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來泰國打工揖膜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留誓沸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓壹粟,卻偏偏與公主長得像拜隧,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子趁仙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容