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)行定制赢乓。
在以下示例中忧侧,MyClass
和 MySubclass
都是 Meta 的實例:
class Meta(type):
# 繼承了 type 作為元類
pass
class MyClass(metaclass=Meta):
# 顯示指定元類 Meta
pass
class MySubclass(MyClass):
# 繼承了父類,父類是元類
pass
如之前所說牌芋, Human
是 type
類型蚓炬,而 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)建類對象。
解析 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
算法查找繼承父類的順序列表是如何生成的寿冕。
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), b
的 type
竟然是 <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)容龄砰。
所以說盟猖, __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)中存在重疊時不會在同一個類中搜索兩次。
先來看一個簡單地多繼承:
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__
搜索順序。
可以計算出 C
的 MRO
:
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)容就先到這里行贪,有興趣的可以查看官方文檔: