Python - 高級教程 - 數(shù)據(jù)模型(4) - 元類與多繼承

Python - 高級教程 - 數(shù)據(jù)模型(4) - 元類與多繼承

上一章節(jié)券时,我們了解了 python 中類的創(chuàng)建和類的基本的屬性谴餐,本章節(jié)我們將主要講解元類與多繼承妙蔗。

元類

默認(rèn)情況下蚕冬,類是使用 type() 來構(gòu)建的柱宦。
元類就是 Python 中用來創(chuàng)建類的類。

類體會在一個新的命名空間內(nèi)執(zhí)行播瞳,類名會被局部綁定到 type(name, bases, namespace) 的結(jié)果掸刊。

類創(chuàng)建過程可通過在定義行傳入 metaclass 關(guān)鍵字參數(shù),或是通過繼承一個包含此參數(shù)的現(xiàn)有類來進(jìn)行定制赢乓。

在以下示例中忧侧,MyClassMySubclass 都是 Meta 的實例:

class Meta(type):
    # 繼承了 type 作為元類
    pass

class MyClass(metaclass=Meta):
    # 顯示指定元類 Meta
    pass

class MySubclass(MyClass):
    # 繼承了父類,父類是元類
    pass

如之前所說牌芋, Humantype 類型蚓炬,而 Human又是一個類,所以type 其實是一個用來創(chuàng)建類的類躺屁,即元類肯夏。

那么我們定義 Human 的過程,即 type 類創(chuàng)建 type 類型的實例的過程犀暑。

Human1 = type("Human1", (object,), dict(name="", age="", sex=""))

通過內(nèi)置關(guān)鍵字 type驯击,通過參數(shù)Human1 作為類名,object 作為繼承的父類耐亏,dict()作為創(chuàng)建類的成員變量徊都,成功的創(chuàng)建了一個類Human1

在類定義內(nèi)指定的任何其他關(guān)鍵字參數(shù)都會在下面所描述的所有元類操作中進(jìn)行傳遞广辰。

當(dāng)一個類定義被執(zhí)行時暇矫,將發(fā)生以下步驟:

  • 解析 MRO 條目;
  • 確定適當(dāng)?shù)脑悾?/li>
  • 準(zhǔn)備類命名空間择吊;
  • 執(zhí)行類主體李根;
  • 創(chuàng)建類對象。
image

解析 MRO 條目

MRO 即【方法解析順序】(Method Resolution Order)几睛。

此屬性是由類組成的元組房轿,在方法解析期間會基于它來查找基類。

C3算法

python 在發(fā)展過程中,也不斷的進(jìn)化了它的 MRO 算法冀续,當(dāng)前是 C3 算法琼讽。
C3 算法保證了即使存在 '鉆石形' 繼承結(jié)構(gòu)即有多條繼承路徑連到一個共同祖先也能保持正確的行為必峰。

C3 算法規(guī)則

以 class A(B,C) 為例:

  • MRO(object) = [object]
  • MRO(A(B, C)) = [A] + merge(MRO(B)洪唐, MRO(C), [B, C])

這里的關(guān)鍵在于 merge,其輸入是一組列表吼蚁,按照如下方式輸出一個列表:

  • 檢查第一個列表的頭元素(如 L[B] 的頭)凭需,記作 H。
  • 若 H 未出現(xiàn)在其它列表的尾部肝匆,則將其輸出粒蜈,并將其從所有列表中刪除
  • 否則,取出下一個列表的頭部記作 H旗国,繼續(xù)該步驟
  • 重復(fù)上述步驟:
  • 如果是列表為空枯怖,則算法結(jié)束;
  • 如果是不能再找出可以輸出的元素能曾,說明無法構(gòu)建繼承關(guān)系度硝,Python 會拋出異常。

下面通過一個例子講解 C3 算法查找繼承父類的順序列表是如何生成的寿冕。

image
class A(object):
    pass

class B(object):
    pass

class C(A, B):
    pass

class D(A):
    pass

class E(D, C):
    pass

print(A.mro())
print(B.mro())
print(C.mro())
print(D.mro())
print(E.mro())

這里我們先不公布結(jié)果蕊程,先利用 C3 算法解析以下,是否與輸出相同驼唱?

- MRO(A) = [A] + merge(MRO(object))
         = [A, object]

- MRO(B) = [B] + merge(MRO(object))
         = [B, object]

- MRO(D(A)) = [D] + merge(MRO(A), [A])
            = [D] + merge([A, object], [A])
            # 此處 遍歷 A藻茂,A出現(xiàn)在 [A] 中且是第一個,刪除 A玫恳,并入 [D]
            = [D, A] + merge([object], [])
            # 此處 遍歷 object辨赐, object 沒有出現(xiàn)在其他列表中, 刪除 object京办,并入[D, A]
            = [D, A, object] + merge([], [])
            # 列表為空
            = [D, A, object]
- MRO(C(A,B)) = [C] + merge(MRO(A), MRO(B), [A, B])
              = [C] + merge([A, object], [B, object], [A, B])
              # 此處 遍歷 A肖油, A 出現(xiàn)在 [A, B] 中且是第一個值,則 刪除 A, 并入 [C]
              = [C, A] + merge([object], [B, object], [B])
              # 此處 遍歷 object臂港, object出現(xiàn)在 [B,object] 中森枪,但不是第一個,繼續(xù)遍歷
              # 此處 遍歷 B, B 出現(xiàn)在 [B] 中审孽,且是第一個值县袱, 則 刪除 B, 并入 [C, A]
              = [C, A, B] + merge([object], [object], [])
              # 此處遍歷 object, object 出現(xiàn)在第二個列表中佑力,則刪除 object, 并入 [C, A, B]
              = [C, A, B, object] + merge([], [], [])
              # 列表 為空
              = [C, A, B, object]
- MRO(E(D, C)) = [E] + merge(MRO(D), MRO(C), [D, C])
               = [E] + merge([D, A, object], [C, A, B, object], [D, C])
               # 此處遍歷 D式散, D出現(xiàn)在 [D,C ] 中,且是第一個打颤,刪除 D暴拄,并入 [E]
               = [E, D] + merge([A, object], [C, A, B, object], [C])
               # 此處 遍歷 A , A 出現(xiàn)在 [C, A, B, object] 中漓滔,但不是第一個,繼續(xù)遍歷
               # 此處 遍歷 object乖篷, object出現(xiàn)在 [C, A, B, object] 中响驴,但不是第一個,繼續(xù)遍歷
               # 此處 遍歷 C撕蔼,C 出現(xiàn)在 [c] 中且是第一個豁鲤,刪除 C, 并入 [E, D]
               = [E, D, C] + merge([A, object], [A, B, object], [])
               # 此處 遍歷 A,A出現(xiàn)在 [A, B, object ] 中鲸沮,且是第一個琳骡,刪除 A, 并入 [E, D, C]
               = [E, D, C, A] + merge([object], [B, object], [])
               # 此處 遍歷 object讼溺, object出現(xiàn)在 [B, object] 中楣号,但不是第一個,繼續(xù)遍歷
               # 此處 遍歷 B怒坯, B 沒有出現(xiàn)在 其他列表中炫狱,刪除 B,并入 [E, D, C, A]
               = [E, D, C, A, B] + merge([object], [object], [])
               = [E, D, C, A, B, object] + merge([], [], [])
               = [E, D, C, A, B, object]

最后的值為 :

A = [A, object]
B = [B, object]
C = [C, A, B, object]
D = [D, A, object]
E = [E, D, C, A, B, object]

執(zhí)行輸出結(jié)果如下:

[<class '__main__.A'>, <class 'object'>]
[<class '__main__.B'>, <class 'object'>]
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
[<class '__main__.D'>, <class '__main__.A'>, <class 'object'>]
[<class '__main__.E'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

輸出的值與推算的結(jié)果相同敬肚。

確定適當(dāng)?shù)脑?/h2>

為一個類定義確定適當(dāng)?shù)脑愂歉鶕?jù)以下規(guī)則:

  • 如果沒有基類且沒有顯式指定元類毕荐,則使用 type();
  • 如果給出一個顯式元類而且 不是 type() 的實例艳馒,則其會被直接用作元類憎亚;
  • 如果給出一個 type() 的實例作為顯式元類,或是定義了基類弄慰,則使用最近派生的元類第美。

最近派生的元類會從顯式指定的元類(如果有)以及所有指定的基類的元類(即 type(cls))中選取。
最近派生的元類應(yīng)為 所有 這些候選元類的一個子類型陆爽。
如果沒有一個候選元類符合該條件什往,則類定義將失敗并拋出 TypeError。

準(zhǔn)備類命名空間

一旦適當(dāng)?shù)脑惐淮_定慌闭,則類命名空間將會準(zhǔn)備好别威。如果元類具有 __prepare__ 屬性,它會以 namespace = metaclass.__prepare__(name, bases, **kwds) 的形式被調(diào)用(其中如果有附加的關(guān)鍵字參數(shù)驴剔,應(yīng)來自類定義)省古。

如果元類沒有 __prepare__ 屬性,則類命名空間將初始化為一個空的有序映射丧失。

這里我們來說一下命名空間 [namespace]

namespace (命名空間)是一個從名字到對象的映射豺妓。

大部分命名空間當(dāng)前都由 Python 字典實現(xiàn),但一般情況下基本不會去關(guān)注它們(除了要面對性能問題時),而且也有可能在將來更改琳拭。

下面是幾個命名空間的例子:

  • 存放內(nèi)置函數(shù)的集合(包含 abs() 這樣的函數(shù)训堆,和內(nèi)建的異常等);
  • 模塊中的全局名稱白嘁;函數(shù)調(diào)用中的局部名稱坑鱼。
  • 從某種意義上說,對象的屬性集合也是一種命名空間的形式权薯。

關(guān)于命名空間的重要一點是姑躲,不同命名空間中的名稱之間絕對沒有關(guān)系睡扬!關(guān)于這點盟蚣,通過以下例子可以了解

class A:
    a = 1
class B:
    b = 2

A.a 
B.a

盡管 A、B 處于同一命名空間卖怜,但是他們還包含自己的命名空間屎开,而自己的命名空間中的屬性名稱之前是毫無關(guān)聯(lián)的。

在不同時刻創(chuàng)建的命名空間擁有不同的生存期

  • 包含內(nèi)置名稱的命名空間是在 Python 解釋器啟動時創(chuàng)建的马靠,永遠(yuǎn)不會被刪除奄抽。
  • 模塊的全局命名空間在模塊定義被讀入時創(chuàng)建;通常甩鳄,模塊命名空間也會持續(xù)到解釋器退出逞度。
  • 被解釋器的頂層調(diào)用執(zhí)行的語句,從一個腳本文件讀取或交互式地讀取妙啃,被認(rèn)為是 main 模塊調(diào)用的一部分档泽,因此它們擁有自己的全局命名空間。(內(nèi)置名稱實際上也存在于一個模塊中揖赴;這個模塊稱作 builtins 馆匿。)
  • 一個函數(shù)的本地命名空間在這個函數(shù)被調(diào)用時創(chuàng)建,并在函數(shù)返回或拋出一個不在函數(shù)內(nèi)部處理的錯誤時被刪除燥滑。(事實上渐北,比起描述到底發(fā)生了什么,忘掉它更好铭拧。)當(dāng)然赃蛛,每次遞歸調(diào)用都會有它自己的本地命名空間。
  • 一個 作用域 是一個命名空間可直接訪問的 Python 程序的文本區(qū)域搀菩。 這里的 “可直接訪問” 意味著對名稱的非限定引用會嘗試在命名空間中查找名稱呕臂。

而 命名空間的搜索規(guī)則如下:

  • 最先搜索的最內(nèi)部作用域包含局部名稱
  • 從最近的封閉作用域開始搜索的任何封閉函數(shù)的范圍包含非局部名稱,也包括非全局名稱
  • 倒數(shù)第二個作用域包含當(dāng)前模塊的全局名稱
  • 最外面的范圍(最后搜索)是包含內(nèi)置名稱的命名空間

關(guān)于全局變量:

  • 如果一個名稱被聲明為全局變量秕磷,則所有引用和賦值將直接指向包含該模塊的全局名稱的中間作用域诵闭。
  • 要重新綁定在最內(nèi)層作用域以外找到的變量,可以使用 nonlocal 語句聲明為非本地變量。
  • 如果沒有被聲明為非本地變量疏尿,這些變量將是只讀的(嘗試寫入這樣的變量只會在最內(nèi)層作用域中創(chuàng)建一個 新的 局部變量瘟芝,而同名的外部變量保持不變)。

通常褥琐,當(dāng)前局部作用域?qū)ⅲò醋置嫖谋荆┮卯?dāng)前函數(shù)的局部名稱锌俱。 在函數(shù)以外,局部作用域?qū)⒁门c全局作用域相一致的命名空間:模塊的命名空間敌呈。 類定義將在局部命名空間內(nèi)再放置另一個命名空間贸宏。

重要的是應(yīng)該意識到作用域是按字面文本來確定的:在一個模塊內(nèi)定義的函數(shù)的全局作用域就是該模塊的命名空間,無論該函數(shù)從什么地方或以什么別名被調(diào)用磕洪。 另一方面吭练,實際的名稱搜索是在運(yùn)行時動態(tài)完成的 --- 但是,語言定義在 編譯時 是朝著靜態(tài)名稱解析的方向演化的析显,因此不要過于依賴動態(tài)名稱解析鲫咽! (事實上,局部變量已經(jīng)是被靜態(tài)確定了谷异。)

Python 的一個特殊之處在于 -- 如果不存在生效的 global 語句 -- 對名稱的賦值總是進(jìn)入最內(nèi)層作用域分尸。 賦值不會復(fù)制數(shù)據(jù) --- 它們只是將名稱綁定到對象。 刪除也是如此:語句 del x 會從局部命名空間的引用中移除對 x 的綁定歹嘹。

事實上箩绍,所有引入新名稱的操作都使用局部作用域:

  • import 語句和函數(shù)定義會在局部作用域中綁定模塊或函數(shù)名稱。
  • global 語句可被用來表明特定變量生存于全局作用域并且應(yīng)當(dāng)在其中被重新綁定尺上;
  • nonlocal 語句表明特定變量生存于外層作用域中并且應(yīng)當(dāng)在其中被重新綁定材蛛。

執(zhí)行類主體

類主體會以(類似于)exec(body, globals(), namespace) 的形式被執(zhí)行。普通調(diào)用與 exec() 的關(guān)鍵區(qū)別在于當(dāng)類定義發(fā)生于函數(shù)內(nèi)部時尖昏,詞法作用域允許類主體(包括任何方法)引用來自當(dāng)前和外部作用域的名稱仰税。

但是,即使當(dāng)類定義發(fā)生于函數(shù)內(nèi)部時抽诉,在類內(nèi)部定義的方法仍然無法看到在類作用域?qū)哟紊隙x的名稱陨簇。類變量必須通過實例的第一個形參或類方法來訪問,或者是通過下一節(jié)中描述的隱式詞法作用域的 __class__ 引用迹淌。

創(chuàng)建類對象

一旦執(zhí)行類主體完成填充類命名空間河绽,將通過調(diào)用 metaclass(name, bases, namespace, **kwds) 創(chuàng)建類對象(此處的附加關(guān)鍵字參數(shù)與傳入 __prepare__ 的相同)。

如果類主體中有任何方法引用了 __class__super唉窃,這個類對象會通過零參數(shù)形式的 super(). __class__ 所引用耙饰,這是由編譯器所創(chuàng)建的隱式閉包引用。這使用零參數(shù)形式的 super() 能夠正確標(biāo)識正在基于詞法作用域來定義的類纹份,而被用于進(jìn)行當(dāng)前調(diào)用的類或?qū)嵗齽t是基于傳遞給方法的第一個參數(shù)來標(biāo)識的苟跪。

在 CPython 3.6 及之后的版本中廷痘,__class__ 單元會作為類命名空間中的條目被傳給元類。 如果存在件已,它必須被向上傳播給type.__new__調(diào)用笋额,以便能正確地初始化該類

當(dāng)使用默認(rèn)的元類 type 或者任何最終會調(diào)用 type.__new__ 的元類時,以下額外的自定義步驟將在創(chuàng)建類對象之后被發(fā)起調(diào)用:

  • 首先篷扩,type.__new__ 將收集類命名空間中所有定義了 __set_name__() 方法的描述器兄猩;
  • 接下來,所有這些 __set_name__ 方法將使用所定義的類和特定描述器所賦的名稱進(jìn)行調(diào)用鉴未;
  • 最后枢冤,將在新類根據(jù)方法解析順序所確定的直接父類上調(diào)用 init_subclass() 鉤子。

在類對象創(chuàng)建之后铜秆,它會被傳給包含在類定義中的類裝飾器(如果有的話)淹真,得到的對象將作為已定義的類綁定到局部命名空間。

當(dāng)通過 type.__new__ 創(chuàng)建一個新類時羽峰,提供以作為命名空間形參的對象會被復(fù)制到一個新的有序映射并丟棄原對象趟咆。這個新副本包裝于一個只讀代理中添瓷,后者則成為類對象的 __dict__ 屬性梅屉。

實例

下面,我們根據(jù)上面的元類的內(nèi)容鳞贷,寫出了下面這個例子坯汤。

class TestMeta(type):
    def __new__(cls, name, bases, attrs):
        print("i am in test meta __new__")
        print(name, bases, attrs)
        attrs["a"] = 1
        return type.__new__(cls, "A", (object,), dict(A.__dict__))

    @classmethod
    def __prepare__(mcs, name, bases):
        print("i am in test meta __prepare__")
        print(name, bases)
        return {}


class A(object):
    def __init__(self):
        print("i am in A __init__")
        self.a = 1


class B(metaclass=TestMeta):
    b = 1

    def __new__(cls):
        print("i am in B __new__")
        return super().__new__(cls)


if __name__ == "__main__":
    b = B()
    print(b.a)
    print(type(b))
    print(B.__dict__)
    print(A.__dict__)
    a = A()
    print(a.a)
    print(type(a))

    print(type(A))
    print(type(B))

i am in test meta __prepare__
B ()
i am in test meta __new__
B () {'__module__': '__main__', '__qualname__': 'B', 'b': 1, '__new__': <function B.__new__ at 0x1032231e0>, '__classcell__': <cell at 0x1031efa98: empty>}
Python/Python2/3-1.py:21: DeprecationWarning: __class__ not set defining 'B' as <class '__main__.A'>. Was __classcell__ propagated to type.__new__?
  class B(metaclass=TestMeta):
i am in A __init__
1
<class '__main__.A'>
{'__module__': '__main__', '__init__': <function A.__init__ at 0x103223158>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
{'__module__': '__main__', '__init__': <function A.__init__ at 0x103223158>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
i am in A __init__
1
<class '__main__.A'>

我們發(fā)現(xiàn), btype 竟然是 <class '__main__.A'>搀愧。也就是說惰聂,通過元類的指定,我們定制化了 B 類的實例的創(chuàng)建過程咱筛,偷梁換柱搓幌,將B() 返回了 A 的實例 a

具體的實現(xiàn)就是:

        return type.__new__(cls, "A", (object,), dict(A.__dict__))

我們來分析一下 B() 的過程:

  • 解析 MRO 條目迅箩,繼承了(<class '__main__.B'>, <class 'object'>)
  • 確定適當(dāng)?shù)脑悾哼@里顯示聲明了為 TestMeta
  • 準(zhǔn)備類命名空間:__prepare__
  • 執(zhí)行類主體:exec
  • 創(chuàng)建類對象:TestMeta.__new__ ==> A
  • 創(chuàng)建類實例:type.__new__

最后返回了 b = B() 已經(jīng)在 創(chuàng)建類對象的時候替換為 A 了溉愁。

類的初始化

了解了類的創(chuàng)建是由 type 元類控制的,那么我們來看下自定義類在除去自定義元類的控制后是如何初始化一個對象的呢饲趋?

我們依舊沿用上面的例子拐揭,StrSub。

class StrSub(str):
    def __init__(self, test):
        print("__init__ begin")
        self.test = test
        print(self.test)
        print("__init__ over")

    def __new__(cls, test):
        print("__new__ begin")
        print(cls)
        print(test)
        print("__new__ over")
        return super().__new__(cls, test)

if __name__ == "__main__":
    ss = StrSub("test")
__new__ begin
<class '__main__.StrSub'>
test
__new__ over
__init__ begin
test
__init__ over

通過上面對 __new____init__ 的了解奕塑,我們可以了解到類初始化的順序堂污。
關(guān)于__new____init__ 的方法的說明,不了解的可以閱讀上一章的內(nèi)容龄砰。

image

所以說盟猖, __new__ 是用來創(chuàng)建類實例的讨衣,而 __init__ 是用來定制化 類實例的。

我們注意到 __new__ 中使用了 super().__new__ 方法來利用父類產(chǎn)生子對象式镐。

super().__new__(cls, test)

super() 和 多繼承

提到類的繼承值依,就離不開多繼承,就離不開父類碟案。

其實單繼承比較好理解愿险,這里就不在贅述,我們來看下一個多繼承的問題价说。

多繼承

對于多數(shù)應(yīng)用來說辆亏,在最簡單的情況下,你可以認(rèn)為搜索從父類所繼承屬性的操作是深度優(yōu)先鳖目、從左至右的扮叨,當(dāng)層次結(jié)構(gòu)中存在重疊時不會在同一個類中搜索兩次。

先來看一個簡單地多繼承:

image
class A(object):
    def __init__(self, a):
        print("A __init__ begin")
        self.a = a
        print("A __init__ end")

    def test(self):
        print("A test begin")
        print(self.a)
        print("A test end")


class B(object):
    def __init__(self, b):
        print("B __init__ begin")
        self.b = b
        print("B __init__ end")

    def test(self):
        print("B test begin")
        print(self.b)
        print("B test end")


class C(A, B):
    def __init__(self, a):
        print("C __init__ begin")
        A.a = a
        B.b = a
        super(C, self).__init__(a)
        print("C __init__ end")

    def __new__(cls, a):
        print("C __new__ begin")
        print(a)
        print("C __new__ end")
        return super().__new__(cls)

    def test(self):
        print("C test begin")
        print(self.a)
        print("C test end")


if __name__ == "__main__":
    c = C("c")
    d = C.mro()
    print(d)
    c.test()
C __new__ begin
c
C __new__ end
C __init__ begin
A __init__ begin
A __init__ end
C __init__ end
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
C test begin
c
C test end

盡管 類 C 繼承了 A,B 兩個類领迈,但是執(zhí)行 __init__ 的確只有 A 彻磁,這里其實就對應(yīng)上了之前所說的 __mro__ 搜索順序。

可以計算出 CMRO:

MRO(C(A,B)) = [C, A, B, object]

因此狸捅,首先回到會到 A 中搜索 __init__衷蜓,然后(遞歸地)到 A 的基類中搜索,如果在那里未找到尘喝,再到 B 中搜索磁浇,依此類推。

所以我們改變一下上面的列子:刪除掉 A__init__ 方法朽褪。

class A(object):
    # def __init__(self, a):
    #     print("A __init__ begin")
    #     self.a = a
    #     print("A __init__ end")

    def test(self):
        print("A test begin")
        print(self.a)
        print("A test end")


class B(object):
    def __init__(self, b):
        print("B __init__ begin")
        self.b = b
        print("B __init__ end")

    def test(self):
        print("B test begin")
        print(self.b)
        print("B test end")


class C(A, B):
    def __init__(self, a):
        print("C __init__ begin")
        A.a = a
        B.b = a
        super(C, self).__init__(a)
        print("C __init__ end")

    def __new__(cls, a):
        print("C __new__ begin")
        print(a)
        print("C __new__ end")
        return super().__new__(cls)

    def test(self):
        print("C test begin")
        print(self.a)
        print("C test end")

if __name__ == "__main__":
    c = C("c")
    d = C.mro()
    print(d)
    c.test()
C __new__ begin
c
C __new__ end
C __init__ begin
B __init__ begin
B __init__ end
C __init__ end
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
C test begin
c
C test end

果然置吓,當(dāng) 第一順位 A 沒有找到方法是, super().__init__() 搜索到了 B.__init__()缔赠,并執(zhí)行了它衍锚。

真實情況比這個更復(fù)雜一些;方法解析順序會動態(tài)改變以支持對 super() 的協(xié)同調(diào)用嗤堰。 這種方式在某些其他多重繼承型語言中被稱為后續(xù)方法調(diào)用戴质,它比單繼承型語言中的 super 調(diào)用更強(qiáng)大。

動態(tài)改變順序是有必要的梁棠,因為所有多重繼承的情況都會顯示出一個或更多的菱形關(guān)聯(lián)(即至少有一個父類可通過多條路徑被最底層類所訪問)置森。

使用 __new__ 的實例

我們需要定制一個類 UpperStr, 用來存儲字符串,但是會將字符串自動轉(zhuǎn)為大寫的符糊。

根據(jù)我們上面了解的 類 的常見過程凫海,我們可以寫出如下代碼。

class UpperStr(str):
    def __init__(self, string):
        print("__init__ begin")
        self.test = string
        print(self.test)
        print("__init__ over")

    def __new__(cls, string):
        print("__new__ begin")
        print(cls)
        print(string)
        print("__new__ over")
        string = string.upper()
        return super(UpperStr, cls).__new__(cls, string)


if __name__ == "__main__":
    ss = UpperStr("test")
    print(ss)

我們只需要在創(chuàng)建 str 對象之前男娄,講傳入 str 的值使用 upper() 函數(shù)變?yōu)槿髮懫磳懠纯伞?/p>

關(guān)于元類與類初始化的內(nèi)容就先到這里行贪,有興趣的可以查看官方文檔:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漾稀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子建瘫,更是在濱河造成了極大的恐慌崭捍,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啰脚,死亡現(xiàn)場離奇詭異殷蛇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)橄浓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門粒梦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荸实,你說我怎么就攤上這事匀们。” “怎么了准给?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵泄朴,是天一觀的道長。 經(jīng)常有香客問我露氮,道長祖灰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任沦辙,我火速辦了婚禮夫植,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘油讯。我一直安慰自己,他們只是感情好延欠,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布陌兑。 她就那樣靜靜地躺著,像睡著了一般由捎。 火紅的嫁衣襯著肌膚如雪兔综。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天狞玛,我揣著相機(jī)與錄音软驰,去河邊找鬼。 笑死心肪,一個胖子當(dāng)著我的面吹牛锭亏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播硬鞍,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼慧瘤,長吁一口氣:“原來是場噩夢啊……” “哼戴已!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起锅减,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤糖儡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后怔匣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體握联,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年每瞒,在試婚紗的時候發(fā)現(xiàn)自己被綠了拴疤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡独泞,死狀恐怖呐矾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情懦砂,我是刑警寧澤蜒犯,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站荞膘,受9級特大地震影響罚随,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜羽资,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一淘菩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屠升,春花似錦潮改、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昌阿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工阿蝶, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人黄绩。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓羡洁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宝与。 傳聞我的和親對象是個殘疾皇子焚廊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

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

  • 從4月7號以來冶匹,到今天整整一個月,我差不多天天堅持在簡書上寫文章咆瘟,一共寫了35篇嚼隘。也許在別人看來這大多數(shù)都是廢話,...
    大盈不若缺閱讀 189評論 0 2
  • 今天剛好是這個群建立的第30天卧檐,也就是我們約定的那個日子,12月26號焰宣。 不禁又要感嘆霉囚,時間過得可真是快。 一個月...
    是布拉拉呀閱讀 313評論 0 1
  • 在使用vue開發(fā)的時候匕积,幾乎每個項目里面都會有導(dǎo)航菜單切換盈罐,不管是頂部導(dǎo)航菜單、還是側(cè)邊導(dǎo)航菜單闪唆,切換到當(dāng)前的菜單...
    光頭小青蛙閱讀 9,845評論 0 7
  • 世人都嘆男大當(dāng)婚女大當(dāng)嫁乃天經(jīng)地義悄蕾,玉女配金龜婿才子配佳人不亦說乎票顾,可事實證明好的婚姻有時還得講究門當(dāng)戶對的好啊。...
    瑩魅兒閱讀 294評論 0 0