1. 版本支持 / 寫法差異
在Python 2.x 中
如果你至今使用的還是 Python 2.x,那麼你需要了解一下,在Python 2.x中存在着兩種類:經典類和新式類。
什麼是經典類?
# 不繼承自object
class Ming:
pass
什麼是新式類?
# 顯示繼承object
class Ming(object):
pass
在Python 3.x 中
如果你已經摒棄了Python 2.x,而已經完全投入了Python 3.x的懷抱,那你可能不需要關心,因爲在Python3.x中所有的類都是新式類(所有類都(顯示/隱式)繼承自object)。
有如下三種寫法,它們是等價的。
class Ming:
pass
class Ming():
pass
class Ming(object):
pass
2. 使用方法 / 獨特屬性
經典類無法使用super()
經典類的類型是 classobj
新式類的類型是 type,保持class與type的統一。
新式類增加了__slots__內置屬性, 可以把實例屬性的種類鎖定到__slots__規定的範圍之中.
新式類增加了__getattribute__方法,在獲取屬性時可以進入此方法。而經典類不會。
# 經典類:py2.7
class Kls01:
def __getattribute__(self, *args, **kwargs):
print("MING - 01")
# 新式類
class Kls02(object):
def __getattribute__(self, *args, **kwargs):
print("MING - 02")
kls02 = Kls02()
kls02.name
kls01 = Kls01()
kls01.name
輸出如下
MING - 02
Traceback (most recent call last):
File "F:/Python Script/Tornado/yang.py", line 13, in <module>
kls01.name
AttributeError: Kls01 instance has no attribute 'name'
3. MRO 查找算法的演變
經典類中
經典類,MRO採用的是深度優先
算法。
爲了驗證,這個繼承順序。在 Python2.x 中可以藉助自帶模塊 inspect 來檢驗。
import inspect
class A:pass
class B(A):pass
class C(A):pass
class D(B, C):pass
print inspect.getmro(D)
輸出如下,可以看出,繼承順序是 D -> B -> A -> C
(<class __main__.D at 0x0000000005836A08>,
<class __main__.B at 0x0000000005836768>,
<class __main__.A at 0x00000000058368E8>,
<class __main__.C at 0x0000000005836AC8>)
非常好理解,但是在菱形繼承時,方法的調用會出現問題。
假設 d 是 D 的一個實例,那麼執行 d.show()是調用 A.show() 呢 還是調用 C.show()呢?
在經典類中,由於是深度優先,所以是會選擇 A.show()。但是很明顯,C.show() 是 A.show() 的更具體化版本(顯示了更多的信息),但我們的x.show() 沒有調用它,而是調用了 A.show(),這顯然是不合理的。所以這纔有了後來的一步一步優化。
新式類中
爲解決經典類 MRO 所存在的問題,Python 2.2 針對新式類提出了一種新的 MRO 計算方式:在定義類時就計算出該類的 MRO 並將其作爲類的屬性。
Python 2.2 的新式類 MRO 計算方式和經典類 MRO 的計算方式非常相似:它仍然採用從左至右的深度優先遍歷,但是如果遍歷中出現重複的類,只保留最後一個。重新考慮上面「菱形繼承」的例子:
同樣地,我們也來驗證一下。另說明,在新式類中,除用inspect外,可以直接通過__mro__屬性獲取類的 MRO。
import inspect
class A(object):pass
class B(A):pass
class C(A):pass
class D(B, C):pass
# 或者通過 D.__mro__ 查找
print inspect.getmro(D)
輸出如下,可以看出,繼承順序變成了 D -> B -> C -> A
(<class '__main__.D'>,
<class '__main__.B'>,
<class '__main__.C'>,
<class '__main__.A'>,
<type 'object'>)
這下,菱形問題解決了。
再來看一個複雜一點的例子。
如果只依靠上面的算法,我們來一起算下,其繼承關係是怎樣的。
- 首先進行深度遍歷:[C, A, X, object, Y, object, B, Y, object, X, object];
- 然後,只保留重複元素的最後一個:[C, A, B, Y, X, object]。
同樣來驗證一下。
class X(object): pass
class Y(object): pass
class A(X, Y): pass
class B(Y, X): pass
class C(A, B): pass
print(C.__mro__)
輸出報錯,它告訴我們 X,Y 具有二義性的繼承關係(這是從Python 2.3後的 C3算法 纔有的)。
Traceback (most recent call last):
File "F:/Python Script/Tornado/yang.py", line 7, in <module>
class C(A, B): pass
TypeError: Error when calling the metaclass bases
Cannot create a consistent method resolution
order (MRO) for bases X, Y
具體爲什麼會這樣,我們來看一下。
對於 A 來說,其搜索順序爲[A, X, Y, object];
對於 B,其搜索順序爲 [B, Y, X, object];
對於 C,其搜索順序爲[C, A, B, X, Y, object]。
我們會發現,B 和 C 中 X、Y 的搜索順序是相反的!也就是說,當 B 被繼承時,它本身的行爲竟然也發生了改變,這很容易導致不易察覺的錯誤。此外,即使把 C 搜索順序中 X 和 Y 互換仍然不能解決問題,這時候它又會和 A 中的搜索順序相矛盾。
對於複雜一點的繼承關係,我們在寫代碼的時候最好做到心中有數。接下來,就教教你,如何在層層複雜的繼承關係中,計算出繼承順序。
例如下面這張圖。
計算過程,會採用一種 merge算法。它的基本思想如下:
- 檢查第一個列表的頭元素(如 L[B1] 的頭),記作 H。
- 若 H 未出現在其它列表的尾部,則將其輸出,並將其從所有列表中刪除,然後回到步驟1;否則,取出下一個列表的頭部記作 H,繼續該步驟。
- 重複上述步驟,直至列表爲空或者不能再找出可以輸出的元素。如果是前一種情況,則算法結束;如果是後一種情況,說明無法構建繼承關係,Python 會拋出異常。
你可以在草稿紙上,參照上面的merge算法,寫出如下過程
L[object] = [object]
L[D] = [D, object]
L[E] = [E, object]
L[F] = [F, object]
L[B] = [B, D, E, object]
L[C] = [C, D, F, object]
L[A] = [A] + merge(L[B], L[C], [B], [C])
= [A] + merge([B, D, E, object], [C, D, F, object], [B], [C])
= [A, B] + merge([D, E, object], [C, D, F, object], [C])
= [A, B, C] + merge([D, E, object], [D, F, object])
= [A, B, C, D] + merge([E, object], [F, object])
= [A, B, C, D, E] + merge([object], [F, object])
= [A, B, C, D, E, F] + merge([object], [object])
= [A, B, C, D, E, F, object]
當然,可以用代碼驗證類的 MRO,上面的例子可以寫作:
class D(object): pass
class E(object): pass
class F(object): pass
class B(D, E): pass
class C(D, F): pass
class A(B, C): pass
A.__mro__
輸出如下
(<class '__main__.A'>,
<class '__main__.B'>,
<class '__main__.C'>,
<class '__main__.
附錄:參考文章
- https://www.python.org/download/releases/2.3/mro/
- https://www.cnblogs.com/whatisfantasy/p/6046991.html
文末福利
本人原創的 《PyCharm 中文指南》一書前段時間一經發布,就火爆了整個 Python 圈,發佈僅一天的時間,下載量就突破了 1000 ,並且在當天就在 Github 上就收穫了數百的 star,截至目前,下載量已經破萬。
這本書一共將近 200 頁,內含大量的圖解,製作之精良,值得每個 Python 工程師 人手一份。
爲方便你下載,我將這本書上傳到 百度網盤上了,你可以自行獲取。
鏈接:https://pan.baidu.com/s/1-NzATHFtaTV1MQzek70iUQ
密碼:mft3