第6篇:Python類的__new__和__init__執(zhí)行原理

__ new__ 是Python類中最容易濫用的功能之一。 它晦澀難懂,到處都是陷阱,當您確實需要 __ new __ 時,它的功能就非常強大且難以理解。__ new __ 的主要用例在元類中。 元類非常復(fù)雜,相信深究Python元類編程的小伙,有本書值得你去讀《Python Cookbook 第三版》但這本書要求讀者最起碼要有Python中級程度的基礎(chǔ),因為作者只給出大量用例,而甚少介紹Python解析器的執(zhí)行原理,而元類編程的基礎(chǔ)大量使用了魔術(shù)方法,其中充滿陷進,比較難懂的就數(shù)__ new __。

class Fruint(object):
    def __init__(self,name):
          self.name=name

//構(gòu)造
b=Fruit("banana")

在Python以及許多其他語言中,對象分為兩個步驟:

  1. 生成對象要訪問對象,必須先創(chuàng)建該對象。對象創(chuàng)建就需要為其分配對應(yīng)類型尺寸的內(nèi)存空間,該對象存在的唯一標識符就是其內(nèi)存實體的內(nèi)存地址,由于Python中的所有對象都繼承基類object,因此創(chuàng)建對象會調(diào)用object.__ new__,而且__ new __會返回Fruit類的一個實例對象(通常指定為self)
self=obj.__new__(Fruit)

2 .構(gòu)造對象:生成對象后,Python將該對象實例(,通常用self表示,對于C底層就是該Py結(jié)構(gòu)體的指針)傳遞給該類的構(gòu)造函數(shù) __ init __,這一步通常是對類屬性初始化調(diào)用類內(nèi)部的類方法,例如如下代碼所示

Fruit.__init__(j,"banana")

注意,如果Fruit類重寫了基類object的__ init __,情況會稍微復(fù)雜一些,例如Fruit類重寫了這樣函數(shù)簽名

Fruit. __ new __(cls,name)

此時,不僅有Fruit. __ new __(cls,name)返回對象實例self,而且還有會將參數(shù)name傳遞給Fruit類的 __ init __構(gòu)造函數(shù),也就是說,就要求程序員必須顯式定義Fruit. __ init __(self,name)這樣的自定義構(gòu)造函數(shù)和Fruit. __ new __(cls,name)相對應(yīng)。

重寫__ new__方法

因此我們知道 object .__ new__是對象實例化的默認步驟。 這就是從類創(chuàng)建實例的原因。 這是作為Fruit("banana")的第一部分隱式發(fā)生的。

注意,直到self通過__ init__運行之后才設(shè)置name屬性。 因為object .__ new__不調(diào)用 __ init__。 它們是不同的方法。 如果我們想對對象實例self在構(gòu)造函數(shù)運行之前對其進行操作,就是在類定義中顯式重寫__ new__。

Python允許我們通過__ new__ magic方法覆蓋任何對象的__ new__。

class Fruit(object):
     def __new__(cls,name):
          obj=super(Fruit,cls).__new__(cls)
          obj.name=name

__ new__將而不是實例作為第一個參數(shù)。 由于它創(chuàng)建了一個實例,而使用 super(Fruit,cls). __ new__ (cls)是非常重要,而不能直接調(diào)用object .__ new__; 再次,原因后述。

下面的示例,展示了可以在顯式重寫的__ new__方法中,對定義類屬性進行初始化,Python完全允許你這么做,但我們通常只在Python類的元編程才做這樣的操作,類屬性放在__ init__ 方法下更有意義

class Fruit(object):
    def __new__(cls,name):
        self=super(Fruit,cls).__new__(cls)
        self.name=name
        return self
    
    def __repr__(self):
        return self.name

我們對上面的Fruit類,按照常規(guī)的Python類實例化操作

b=Fruit("banana")

或者,我們顯式執(zhí)行Fruit.__ new__(Fruit,"banana"),輸出的結(jié)果是一樣的,但你會認為Python解釋器在執(zhí)行時,會一樣嗎?讀者請自行思考一下。

上買的示例中,即便我們沒有在Fruit類中顯式定義__ init __方法,但當Python解析器在將Python源代碼序列化為字節(jié)碼的過程中,會隱含地為Fruit類添加默認的構(gòu)造函數(shù)Fruit. __ init __(self,*args,**kwargs),即便這個默認構(gòu)造是不做任何事

Python類的自定義構(gòu)造函數(shù)

OK,我們通過下面幾個反例子,沒錯,老子最喜歡用反面例子來說明問題的,我們Fruit類顯式定義了__ init__構(gòu)造函數(shù),而且是一個默認構(gòu)造,嘗試運行的話出錯,提示的傳入?yún)?shù)的個數(shù)和顯式定義了__ init__構(gòu)造函數(shù)的個數(shù)沖突

ss8.png

到這里應(yīng)該很好理解,我們上文已經(jīng)說過了,我們只要在Fruit類顯式定義Fruit類的自定義構(gòu)造__ init __(self,name)或 __ init __(self,*args,**kwargs)就可以解決問題

或這樣

小結(jié)

  • 重要的事情說多一遍無妨,使用Python類的常規(guī)的實例化語句,例如:b=Fruit("banana"),Python實際上就分兩步執(zhí)行,詳述見上文。

  • 而顯式執(zhí)行Fruit. __ new __ (Fruit,"banana"),只會執(zhí)行__ new __,而不會執(zhí)行 __ init __

  • 自定義的__ new __方法必須返回對象實例,而且對象通常有父類調(diào)用語句super(Fruit,cls). __ new __ (cls),請好好理解為什么要用關(guān)鍵字super,因為關(guān)鍵字super就是告知當前的子類Fruit“我的父類是object,別無他人”,再來個更復(fù)雜一點的

例如類Apple繼承自類Fruit,當然類Fruit就繼承自object,你會認為Apple.實例化時會跳過Fruit,直接調(diào)用object.__ new__(Apple)嗎?一旦你這樣做會令A(yù)pple-->Fruit-->object的任何繼承信息(類屬性和類方法)缺失(生動地說就是有歪輪常),因此super關(guān)鍵字是告知當前子類它指向子類“相對”的父類。

Ok,Python類的__ new__ ,__ init __和super關(guān)鍵字構(gòu)成了Python類繼承的基石,這些原理性的知識點你理解了,那么寫這篇的目地是為Cython類繼承做鋪墊,這個目地就達成了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容