1. 程式人生 > 實用技巧 >Python 中的新式類和經典類的區別?

Python 中的新式類和經典類的區別?

1. 版本支援 / 寫法差異#

在Python 2.x 中

如果你至今使用的還是 Python 2.x,那麼你需要了解一下,在Python 2.x中存在著兩種類:經典類和新式類。

什麼是經典類?


Copy

Copy

# 不繼承自object class Ming: pass 什麼是新式類? # 顯示繼承object class Ming(object): pass

在Python 3.x 中

如果你已經摒棄了Python 2.x,而已經完全投入了Python 3.x的懷抱,那你可能不需要關心,因為在Python3.x中所有的類都是新式類(所有類都(顯示/隱式)繼承自object)。

有如下三種寫法,它們是等價的。


Copy

Copy

class Ming: pass class Ming(): pass class Ming(object): pass

2. 使用方法 / 獨特屬性#

經典類無法使用super()

經典類的型別是 classobj

新式類的型別是 type,保持class與type的統一。

新式類增加了__slots__內建屬性, 可以把例項屬性的種類鎖定到__slots__規定的範圍之中.
新式類增加了__getattribute__方法,在獲取屬性時可以進入此方法。而經典類不會。


Copy

Copy

# 經典類: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

輸出如下


Copy

Copy

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 來檢驗。


Copy

Copy

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


Copy

Copy

(<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。


Copy

Copy

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


Copy

Copy

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

這下,菱形問題解決了。

再來看一個複雜一點的例子。

如果只依靠上面的演算法,我們來一起算下,其繼承關係是怎樣的。

  1. 首先進行深度遍歷:[C, A, X, object, Y, object, B, Y, object, X, object];
  2. 然後,只保留重複元素的最後一個:[C, A, B, Y, X, object]。

同樣來驗證一下。


Copy

Copy

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演算法 才有的)。


Copy

Copy

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演算法。它的基本思想如下:

  1. 檢查第一個列表的頭元素(如 L[B1] 的頭),記作 H。
  2. 若 H 未出現在其它列表的尾部,則將其輸出,並將其從所有列表中刪除,然後回到步驟1;否則,取出下一個列表的頭部記作 H,繼續該步驟。
  3. 重複上述步驟,直至列表為空或者不能再找出可以輸出的元素。如果是前一種情況,則演算法結束;如果是後一種情況,說明無法構建繼承關係,Python 會丟擲異常。

你可以在草稿紙上,參照上面的merge演算法,寫出如下過程


Copy

Copy

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,上面的例子可以寫作:


Copy

Copy

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__

輸出如下


Copy

Copy

(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.

本文首發於python黑洞網,csdn同步更新