python的下劃線,繼承規(guī)則
打星號(hào)原因之一是因?yàn)檫@篇文章內(nèi)容太多,沒有寫完。二是因?yàn)檫@篇文章的難度深度很大,需要仔細(xì)揣摩才可以理解。由于大家對(duì)源碼不熟悉,所以只好用通俗而晦澀的語(yǔ)言解釋出來,抽空再做些補(bǔ)充(已經(jīng)補(bǔ)充完畢)。
不要騙我,你是不是無數(shù)次的被python中坑爹的下劃線弄的暈頭轉(zhuǎn)向的。不瞞你說我也曾經(jīng)被它弄的痛不欲生,那么現(xiàn)在我就幫你分析總結(jié)一下python中的下劃線到底代表什么意思,看過這篇總結(jié)以后,任何下劃線都阻攔不了你reading的腳步。
變量:
python中的權(quán)限采用“你懂的”的規(guī)則,不會(huì)顯式聲明給你,規(guī)則如下:
前面帶“_”的變量,例如:_var,標(biāo)明是一個(gè)私有類型的變量,但是只起到標(biāo)明的作用,外部類還是可以訪問到這個(gè)變量。但是帶有這樣的參數(shù)的變量或者包不能用from module import * 導(dǎo)入
前后帶兩個(gè)“_”的變量,例如:__var,標(biāo)明是內(nèi)置私有變量,外部類無法訪問,甚至是子類對(duì)象也無法訪問。
大寫加下劃線的變量:標(biāo)明是不會(huì)發(fā)生改變的全局變量,例如:USER_CONSTANT,這好比c++中的constant。
函數(shù):
前面帶_的變量:標(biāo)明是私有函數(shù),同理只是用于標(biāo)明而已。
前后兩個(gè)_的函數(shù),標(biāo)明是特殊函數(shù)(一般是module內(nèi)建函數(shù),比如init函數(shù),我們后面會(huì)講到)
注意:雙下劃線對(duì)解釋器有特殊的意義,我們?cè)诿臅r(shí)候一定要盡量避 免這種命名方式
接下來你可以看看這樣的一段代碼,可能會(huì)涉及到python的繼承方式,我們一并也都講了,這段內(nèi)容摘自CSDN某位博主,我就借花獻(xiàn)佛了:
class A(object):
def __init__(self):
self.__private()
self.public()
def __private(self):
print 'A.__private()'
def public(self):
print 'A.public()'
class B(A):
def __private(self):
print 'B.__private()'
def public(self):
print 'B.public()'
b = B()
那么這段代碼的輸出是什么呢?
答案是:A.__private() B.public()
你肯定會(huì)很奇怪,這樣奇葩的輸出到底是怎么回事兒,產(chǎn)生疑問的原因也很簡(jiǎn)單,因?yàn)槟銓?duì)python的機(jī)制還不了解,下面就進(jìn)行分析(本來這一塊是放在python高級(jí)編程里講的,這里遇到了一并就都倒出來吧):
一切都從為什么會(huì)輸出:A.__private()開始。我們還是來看一下python的命名規(guī)則
根據(jù)python tutorial的說法,變量名(標(biāo)識(shí)符)是python的一種原子元素(什么是原子元素,你可以參照數(shù)據(jù)庫(kù)操作的原子性來理解,也就是不可再分的),當(dāng)變量命名被綁定到一個(gè)對(duì)象的時(shí)候,變量名就代指這個(gè)對(duì)象,當(dāng)它出現(xiàn)在代碼塊中,它就是本地變量;當(dāng)它出現(xiàn)在模塊中它就是全局變量。
根據(jù)該理論,我們可以把上述代碼塊分為三個(gè)代碼塊:類A的定義、類B的定義和變量b的定義。類A定義了三個(gè)成員變量,類B定義了兩個(gè)成員變量。當(dāng)我們用dir(A)查看類A里面的東西的時(shí)候,我們發(fā)現(xiàn)了一些有趣的現(xiàn)象。
_A__private
__class__
__delattr__
__dict__
__doc__
__format__
__getattribute__
__hash__
__init__
__module__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
__weakref__
public
實(shí)際上,除去init以外,我們發(fā)現(xiàn)A中我們額外定義的幾個(gè)類屬性變成了這樣:
_A__private
public
很明顯,問題出在了__private()上。這里我們要深究一下python解釋器對(duì)私有類型成員的處理細(xì)節(jié)。
當(dāng)在類中定義了私有變量的時(shí)候,在代碼正式生成以前,python會(huì)自動(dòng)的將這個(gè)私有變量轉(zhuǎn)換為長(zhǎng)格式(變?yōu)楣灿校?/code>轉(zhuǎn)換的機(jī)制是這樣的:
在變量前端插入類名,再在前端插入一個(gè)下劃線字符。
這就是所謂的私有變量軋壓(跟我讀:ya ya)(private name mangling)。這也就是類A中_A__private出現(xiàn)的原因。
(其實(shí)可以從另一個(gè)角度去理解,表面上它聲明為私有變量而禁止別人訪問但是私底下還是要給自己類內(nèi)部的人訪問的,所以要轉(zhuǎn)換一下)
注意1:因?yàn)檐垑簳?huì)使得變量名字變長(zhǎng),所以一旦超過了255個(gè)字符以后,python會(huì)進(jìn)行切斷,要特別注意因此導(dǎo)致的命名沖突。
注意2:當(dāng)類名全部以下劃線命名的時(shí)候,軋壓規(guī)則失效。
嘗試把上述類A換成“_____”
那么當(dāng)類A經(jīng)過軋壓之后,它的代碼變成了:
class A(object):
def __init__(self):
self._A__private() # 這行變了
self.public()
def _A__private(self): # 這行也變了
print 'A.__private()'
def public(self):
print 'A.public()'
因?yàn)轭怋定義的時(shí)候沒有覆蓋init方法,所以調(diào)用的仍然是A.init,即執(zhí)行了self._A__private().(這一點(diǎn)就涉及到下面要講的python的繼承了)
最后在講繼承之前還有一點(diǎn)特別的重要,剛剛dir()出來的一堆東西,除了我們自己定義的,還有系統(tǒng)為每個(gè)類自定義的屬性和方法
,那么它們有什么用呢?我們這里詳細(xì)講解一下:
自定義屬性:
- class.__doc__ 類型幫助信息
- class.__name__ 類型名稱
- class.__dict__ 類型字典,存儲(chǔ)所有該類實(shí)例成員的信息,如果這么說你不明白的話,請(qǐng)自行print 類名.dict觀察。
- class.__class__ 類類型。可能你會(huì)覺得奇怪,那是因?yàn)镻ython里面所有的東西都是對(duì)象,甚至比java都要面向?qū)ο蟆T凇秔ython源碼分析》的時(shí)候我會(huì)講到。
- class.__module__ 類型所在的模塊
自定義方法(也稱保留方法):
目的 代碼 實(shí)際調(diào)用
初始化 x=class() x.__init__()
字符串官方表現(xiàn) repr(x) x.__repr__()
字符串非正式 str(x) x.__str__()
字節(jié)數(shù)組非正式值 bytes(x) x.__bytes__()
格式化字符串的值
format(x,format_spec) x.__format__(format_spec)
注意:
str()一般是將數(shù)值轉(zhuǎn)成字符串。
repr()是將一個(gè)對(duì)象轉(zhuǎn)成字符串顯示,注意只是顯示用,有些對(duì)象轉(zhuǎn)成字符串沒有直接的意思。如list,dict使用str()是無效的,但使用repr可以,這是為了看它們都有哪些值,為了顯示之用。 類似于java的toString方法。
在最后,我們?cè)俳o大家總結(jié)一下python中常見的下劃線函數(shù)的作用以及其調(diào)用情況,這一部分要求多看多想。
part1:迭代器類似類
序號(hào) 目的 所編寫代碼 Python 實(shí)際調(diào)用
① 遍歷某個(gè)序列 iter(seq) seq.__iter__()
② 從迭代器中獲取下一個(gè)值 next(seq) seq.__next__()
③ 按逆序創(chuàng)建一個(gè)迭代器 reversed(seq) seq.__reversed__()
無論何時(shí)創(chuàng)建迭代器都將調(diào)用 __iter__() 方法。這是用初始值對(duì)迭代器進(jìn)行初始化的絕佳之處。
無論何時(shí)從迭代器中獲取下一個(gè)值都將調(diào)用 __next__() 方法。
__reversed__() 方法并不常用。它以一個(gè)現(xiàn)有序列為參數(shù),并將該序列中所有元素從尾到頭以逆序排列生成一個(gè)新的迭代器。
part2 :計(jì)算屬性 (這一部分非常難理解,有時(shí)間我再下面更新講解)
序號(hào) 目的 所編寫代碼 Python 實(shí)際調(diào)用
① 獲取一個(gè)計(jì)算屬性(無條件的) x.my_property x.__getattribute__('my_property')
② 獲取一個(gè)計(jì)算屬性(后備) x.my_property
x.__getattr__('my_property')
③ 設(shè)置某屬性 x.my_property = value x.__setattr__('my_property',value)
④ 刪除某屬性 del x.my_property x.__delattr__('my_property')
⑤ 列出所有屬性和方法 dir(x) x.__dir__()
如果某個(gè)類定義了 __getattribute__() 方法,在 每次引用屬性或方法名稱時(shí) Python 都調(diào)用它(特殊方法名稱除外,因?yàn)槟菢訉?huì)導(dǎo)致討厭的無限循環(huán))。
如果某個(gè)類定義了 __getattr__() 方法,Python 將只在正常的位置查詢屬性時(shí)才會(huì)調(diào)用它。如果實(shí)例 x 定義了屬性 color, x.color 將 不會(huì) 調(diào)用x.__getattr__('color');而只會(huì)返回 x.color 已定義好的值。
無論何時(shí)給屬性賦值,都會(huì)調(diào)用 __setattr__() 方法。
無論何時(shí)刪除一個(gè)屬性,都將調(diào)用 __delattr__() 方法。
如果定義了 __getattr__() 或 __getattribute__() 方法, __dir__() 方法將非常有用。通常,調(diào)用 dir(x) 將只顯示正常的屬性和方法。如果 __getattr()__方法動(dòng)態(tài)處理 color 屬性, dir(x) 將不會(huì)將 color 列為可用屬性。可通過覆蓋 __dir__() 方法允許將 color 列為可用屬性,對(duì)于想使用你的類但卻不想深入其內(nèi)部的人來說,該方法非常有益。
part3:可比較的類
序號(hào) 目的 所編寫代碼 Python 實(shí)際調(diào)用
相等 x == y x.__eq__(y)
不相等 x != y x.__ne__(y)
小于 x < y x.__lt__(y)
小于或等于 x <= y x.__le__(y)
大于 x > y x.__gt__(y)
大于或等于 x >= y x.__ge__(y)
布爾上上下文環(huán)境中的真值 if x: x.__bool__()
如果定義了 __lt__() 方法但沒有定義 __gt__() 方法,Python 將通過經(jīng)交換的算子調(diào)用 __lt__() 方法。然而,Python 并不會(huì)組合方法。例如,如果定義了 __lt__() 方法和 __eq()__ 方法,并試圖測(cè)試是否 x <= y,Python 不會(huì)按順序調(diào)用 __lt__() 和 __eq()__ 。它將只調(diào)用__le__() 方法。
part4:可序列化的類
Python 支持任意對(duì)象的序列化和反序列化。(多數(shù) Python 參考資料稱該過程為 “pickling” 和 “unpickling”)。該技術(shù)對(duì)與將狀態(tài)保存為文件并在稍后恢復(fù)它非常有意義。所有的 內(nèi)置數(shù)據(jù)類型 均已支持 pickling 。如果創(chuàng)建了自定義類,且希望它能夠 pickle,閱讀 pickle 協(xié)議 了解下列特殊方法何時(shí)以及如何被調(diào)用。
序號(hào) 目的 所編寫代碼 Python 實(shí)際調(diào)用
自定義對(duì)象的復(fù)制 copy.copy(x) x.__copy__()
自定義對(duì)象的深度復(fù)制 copy.deepcopy(x) x.__deepcopy__()
在 pickling 之前獲取對(duì)象的狀態(tài) pickle.dump(x, file) x.__getstate__()
序列化某對(duì)象 pickle.dump(x, file) x.__reduce__()
序列化某對(duì)象(新 pickling 協(xié)議) pickle.dump(x, file, protocol_version) x.__reduce_ex__(protocol_version)
* 控制 unpickling 過程中對(duì)象的創(chuàng)建方式 x = pickle.load(file) x.__getnewargs__()
* 在 unpickling 之后還原對(duì)象的狀態(tài) x = pickle.load(file) x.__setstate__()
* 要重建序列化對(duì)象,Python 需要?jiǎng)?chuàng)建一個(gè)和被序列化的對(duì)象看起來一樣的新對(duì)象,然后設(shè)置新對(duì)象的所有屬性。__getnewargs__() 方法控制新對(duì)象的創(chuàng)建過程,而 __setstate__() 方法控制屬性值的還原方式。
part5:something amazing
如果知道自己在干什么,你幾乎可以完全控制類是如何比較的、屬性如何定義,以及類的子類是何種類型。
序號(hào) 目的 所編寫代碼 Python 實(shí)際調(diào)用
類構(gòu)造器 x = MyClass() x.__new__()
*類析構(gòu)器 del x x.__del__()
只定義特定集合的某些屬性
x.__slots__()
自定義散列值 hash(x) x.__hash__()
獲取某個(gè)屬性的值 x.color type(x).__dict__['color'].__get__(x, type(x))
設(shè)置某個(gè)屬性的值 x.color = 'PapayaWhip' type(x).__dict__['color'].__set__(x, 'PapayaWhip')
刪除某個(gè)屬性 del x.color type(x).__dict__['color'].__del__(x)
控制某個(gè)對(duì)象是否是該對(duì)象的實(shí)例 your class isinstance(x, MyClass) MyClass.__instancecheck__(x)
控制某個(gè)類是否是該類的子類 issubclass(C, MyClass) MyClass.__subclasscheck__(C)
控制某個(gè)類是否是該抽象基類的子類 issubclass(C, MyABC) MyABC.__subclasshook__(C)
上面就簡(jiǎn)單科普python下劃線的知識(shí),接著我們談?wù)刾ython的重點(diǎn)問題:繼承
繼承之功能不贅述,繼承之特點(diǎn)也是各有千秋,C++的多繼承,java的單繼承,父類方法的調(diào)用機(jī)制等等,基礎(chǔ)不扎實(shí)也夠喝一壺了。python繼承機(jī)制與平時(shí)所理解的有所不同。
先來說說super,我們平時(shí)把父類也叫做超類,那么super也是調(diào)用父類屬性(attribute)的時(shí)候時(shí)候所用到的關(guān)鍵字,對(duì)沒錯(cuò),關(guān)鍵字,但是super在python里面是一個(gè)內(nèi)建類型,盡管它的使用方法和函數(shù)有點(diǎn)兒類似,但是它實(shí)際上還是一個(gè)內(nèi)建類型。(什么是內(nèi)建類型?顧名思義就是內(nèi)部構(gòu)建的類,諸如None,數(shù)值類型,字符串類型)
>>>super
<type 'super'>
這就證實(shí)了我所言非虛,如果你已經(jīng)習(xí)慣了直接調(diào)用父類并將self作為第一個(gè)參數(shù),來訪問類型的特性,super的用法可能讓你有點(diǎn)兒混亂。(關(guān)于self的用法,我也會(huì)在后面的文章中作為補(bǔ)充,作為最基本的你就記著定義類方法一定要用self作為第一個(gè)參數(shù))我們可以看看下面的代碼:
>>> class Father(object):
... def say(self):
... print('Hello my child')
...
>>> class Child(Father):
... def say(self):
... Father.say(self)
... print('clean your bedroom')
...
>>>Tom = Child()
>>>Tom.say()
Hello my child
clean your bedroom
上述這段代碼是沒有問題,但是你是不是有疑問在里面。粗心的人可能覺得這沒問題啊,先顯示調(diào)用父類,再調(diào)子類。沒錯(cuò),F(xiàn)ather.say( )這一行確實(shí)調(diào)用了超類的say( )方法,將self作為第一個(gè)參數(shù)傳入。但是它傳遞self,是Child的self。拋開這個(gè)疑問不管,我們?cè)賮砜纯慈绻胹uper實(shí)現(xiàn)相同效果的話該怎么用:
>>>class Child(Father):
... def say(self):
... super(Father, self).say()
,,, print 'clean your bedroom'
如上所示,簡(jiǎn)單的二重繼承,你可能覺得問題不大,但面對(duì)多重繼承的時(shí)候,super無論是在使用上還是閱讀上都是非常費(fèi)力的,比較而言第一種不用super的方式還是比較符合繼承邏輯的。
那么在如何避免使用super以及解釋我們剛剛的問題之前,我們先要來看看python中的方法解析順序(MRO)。這個(gè)較之于軋壓,又有所不同。
在python2.3以前的版本(當(dāng)然了,我們現(xiàn)在最常用的是2.7,但是官方力推的是3.0以上的版本,比如html解析器在3.0左右的版本就已經(jīng)支持的不是很好了,我們?cè)陂喿x一門語(yǔ)言規(guī)范的時(shí)候當(dāng)然要比較一下各個(gè)版本的做出了哪一塊兒的改進(jìn)),類繼承是按照舊的C3規(guī)則進(jìn)行的。在舊的C3規(guī)則中,如果一個(gè)類有兩個(gè)祖先,MRO計(jì)算很簡(jiǎn)單:
class Base1 class Base2
\ /
\ /
class MyClass
>>>class Base1:
... pass
...
>>>class Base2:
... def method(self):
... print 'Base2'
...
>>>class MyClass(Base1, Base2):
... pass
...
>>>here.MyClass()
>>>here.method()
Base2
以上的解析順序是:當(dāng)here.method被調(diào)用的時(shí)候,解釋程序?qū)⒉檎襇yClass中的方法,然后在Base1中查找,最后在Base2中查找。
好的,上面的內(nèi)容可能沒什么難事,因?yàn)樗€在我們正常的認(rèn)知范圍內(nèi)~那么現(xiàn)在我們?cè)趦蓚€(gè)父類上面加一個(gè)公共類,然后你再猜一下代碼的輸出是什么。
class BaseBase
/ \
/ \
class Base1 class Base2
\ /
\ /
class MyClass
>>>class BaseBase:
... def method(self):
... print 'BaseBase'
...
>>>class Base1(BaseBase):
... pass
...
>>>class Base2(BaseBase):
... def method(self):
... print 'Base2'
...
>>>class MyClass(Base1, Base2):
... pass
...
>>>here = MyClass()
>>>here.method()
好了,你可以猜一下現(xiàn)在的輸出是什么了,如果你猜不到,那就請(qǐng)繼續(xù)耐心的看完這篇教程。
答案是:BaseBase
我們沒有意圖去解釋舊的python規(guī)則中的這種繼承現(xiàn)象,而且無論是源代碼中還是實(shí)際應(yīng)用中,這種繼承方式也是極為罕見的。但正是由于這種舊的MRO規(guī)則會(huì)產(chǎn)生這種古怪的輸出,2.3以后的較新版本中的輸出變?yōu)榱耍?code>Base2。
如此古怪的輸出結(jié)果,導(dǎo)致我們想問一個(gè)問題,Python的繼承輸出結(jié)果還是不是可以預(yù)測(cè)的?
我們來簡(jiǎn)單解釋一下MRO的規(guī)則:MRO說白了其實(shí)就是一顆繼承樹
我們來檢測(cè)一下MyClass中的繼承順序:
>>>def L(klass):
... print [k.__name__ for k in klass.__mro__]
...
>>>L(MyClass)
['MyClass', 'Base1', 'Base2', 'BaseBase', 'object']
tips:
你也可以直接用print MyClass.__mro__來查看,或者用MyClass.mro(),這就是上面我們講到的下劃線函數(shù)的實(shí)際調(diào)用問題。
那么現(xiàn)在我們就能夠明白,子父類同名方法的調(diào)用是要遵循MRO樹的順序的。然后你還需要記住的是python的調(diào)用都是顯示聲明的
。
希望沒有把你繞暈,讓我們回到super這里來
我們前面已經(jīng)講過了一種多重繼承,當(dāng)發(fā)生多重繼承的時(shí)候,普通的調(diào)用可能也會(huì)陷入困境,那么super就更不必說,多重繼承使用super是相當(dāng)危險(xiǎn)的,原因在于python類的初始化函數(shù),更進(jìn)一步在于python父類的初始化函數(shù)需要我們顯示的調(diào)用
,我們來看看這么一個(gè)歷程,來自:http://fuhm.net/super-harmful
混用super和傳統(tǒng)調(diào)用:
class A(object):
def __init__(self):
print 'A'
super(A,self).__init__()
class B(object):
def __init__(self):
print 'B'
super(B,self).__init__()
class C(A,B):
def __init__(self):
print 'C'
A.__init__(self)
B.__init__(self)
print 'MRO: ', [x.__name__ for x in C.__mro__]
c = C()
大膽猜一下輸出:
MRO: ['C', 'A', 'B', 'object']
C A B B
很詭異啊,為什么是這種輸出,為什么多了一個(gè)B呢?MRO樹里面的關(guān)系明明沒有重復(fù)項(xiàng),而且調(diào)用的順序也是我們按照自己意愿聲明的,為什么多了一個(gè)B?
原因是:C實(shí)例調(diào)用了A.__init_(self),這樣一來,我們?cè)贏中的super(A,self).___init_____( )函數(shù)將調(diào)用B的構(gòu)造程序,還不理解?那么我們看看上面的MyClass的調(diào)用順序就一目了然了,如果它想調(diào)用根類的函數(shù),它是按照優(yōu)先兄弟的順序來調(diào)用的。這么看來super在python的用法就特別的混亂。
我們的經(jīng)驗(yàn)是如果你想將一個(gè)父類子類話,你應(yīng)該先檢查一下這個(gè)父類的mro特性,如果mro不存在,那么說明這個(gè)類是一個(gè)舊式的類,python還沒有將它加入mro特性,那我們?yōu)榱税踩蛻?yīng)該避免使用super。
如果一個(gè)類_mro_特性,則快速的檢查一下MRO所涉及的類的構(gòu)造程序代碼,如果到處都使用了super,那你也可以使用它,否則你就試著保持一致性而不要混用。
那么都使用super,拒絕混用會(huì)不會(huì)達(dá)到理想的效果呢?讓我們來看下面一段代碼:
>>>class BaseBase(object):
def __init__(self):
print 'basebase'
super(BaseBase, self).__init__()
>>>class Base1(BaseBase):
def __init__(self):
print 'base1'
super(Base1, self).__init__()
>>>class Base2(BaseBase):
def __init__(self, arg):
print 'base2'
super(Base2, self)
>>>class MyClass(Base1, Base2):
def __init__(self, arg):
print 'my base'
super(MyClass, self).__init__(arg)
>>>m = MyClass(10)
然后它不僅輸出了my base,而且還輸出了一堆錯(cuò)誤,看了一下TypeError: __init__() tales exactly 1 argument(2 given).
嗯哼,super大法也不是那哪兒都好用的,可能你說,不就是一個(gè)參數(shù)問題么,腦洞開一下,python是可以接受動(dòng)態(tài)參數(shù)的,對(duì)啊,*args和**kw確實(shí)是可以解決這個(gè)問題呢。把上面的:
__init__(self, arg)、__init__(self)
全部改成:__init__(self, *args, **kw)
這樣做確實(shí)解決了我們的bug,贊!得到正確的輸出:
my base
base1
base2
basebase
但是這是一種糟糕的修復(fù)方法,因?yàn)樗顾械臉?gòu)造函數(shù)接受所有類型的參數(shù),就好比屋頂?shù)教幎际锹┒矗叶春艽螅裁椿覊m垃圾蟲子都能進(jìn)來,同理,這會(huì)使我們的代碼變得極度脆弱。
上面我們了解了super濫用導(dǎo)致的代碼可讀性,然后我們也看到了使用顯示調(diào)用也并不能給我們帶來多少優(yōu)惠,其實(shí)我們應(yīng)該抱著這樣一個(gè)思想,本來??就不可能跑得比汽車快,你怎么鞭笞它,也是于事無補(bǔ)的。就像python本來對(duì)多繼承支持的就不是很好,我們何必要強(qiáng)python所難呢。
所以當(dāng)我們用到繼承的時(shí)候,我們一定要提醒自己以下幾點(diǎn):
- 一定要避免多重繼承,如果非用不可,我們會(huì)有相應(yīng)的設(shè)計(jì)模式替代。(后續(xù)再更新)
- 如果不使用多繼承,super的使用就必須一致,在類層次結(jié)構(gòu)中,應(yīng)該在所有的地方都使用super或者是徹底不使用它。
- 不要混用老式類和新式類,如何辨別參見MRO。
- 調(diào)用父類的時(shí)候必須檢查類層次,為了避免出現(xiàn)任何問題,每次調(diào)用父類的時(shí)候我們不要偷懶,我們?cè)诮K端可以快速的查看所涉及的MRO(使用方法上面已經(jīng)說明了)