Python中繼承的優缺點

導語:本文章記錄了本人在學習Python基礎之面向對象篇的重點知識及我的心得,打算入門Python的朋友們能夠來一塊兒學習並交流。

本文重點:python

一、不要試圖在內置類型的子類中重寫方法,能夠繼承collections的可拓展類尋求變通;
二、掌握多重繼承中的MRO和Super;
三、瞭解處理多重繼承的一些建議。

1、子類化內置類型的缺點

一、內置類型的方法不會調用子類覆蓋的方法

內置類能夠子類化,可是內置類型的方法不會調用子類覆蓋的方法。下面以繼承dict的自定義子類重寫__setitem__爲例說明:segmentfault

class ModifiedDict(dict):
    def __setitem__(self, key, value):
        super().__setitem__(key,[value]*2)
a=ModifiedDict(one=1)
a["two"]=2
print(a)
a.update(three=3)
print(a)#輸出{'one': 1, 'two': [2, 2], 'three': 3}

從輸出能夠看到,鍵值對one=1和three=3存入a時均調用了dict的__setitem__,只有[]運算符會調用咱們預先覆蓋的方法。
問題的解決方式在於不去子類化dict,而是子類化colections.UserDict。數據結構

二、子類化collections中的類

用戶自定義的類應該繼承collections模塊,如UserDict,UserList,UserString。這些類作了特殊設計,所以易於拓展。子類化UserDict的代碼以下:app

from collections import UserDict
class ModifiedDict(UserDict):
    def __setitem__(self, key, value):
        super().__setitem__(key,[value]*2)
b=ModifiedDict(one=1)
b["two"]=2
b.update(three=3)
print(b)#輸出{'one': [1, 1], 'two': [2, 2], 'three': [3, 3]}

小結:上述問題只發生在C語言實現的內置類型子類化狀況中,並且隻影響直接繼承內置類型的自定義類。相反,子類化使用Python編寫的類,如UserDict或MutableMapping就不會有此問題。框架

2、多重繼承

一、方法解析順序(Method Resolution Order,MRO)

在多重繼承中存在不相關的祖先類實現同名方法引發的衝突問題,這種問題稱做「菱形問題」。Python依靠特定的順序遍歷繼承圖,這個順序叫作方法解析順序。如圖,左圖是類的UML圖,右圖中的虛線箭頭是方法解析順序。
圖片描述函數

二、super

提到類的屬性__mro__,就會提到super:學習

super 是個類,既不是關鍵字也不是函數等其餘數據結構。

做用:super是子類用來調用父類方法的。
語法:super(a_type, obj);
a_type是obj的__mro__,固然也能夠是__mro__的一部分,同時issubclass(obj,a_type)==truespa

舉個例子, 有個 MRO: [A, B, C, D, E, object]
咱們這樣調用:super(C, A).foo()
super 只會從 C 以後查找,即: 只會在 D 或 E 或 object 中查找 foo 方法。設計

下面構造一個菱形問題的多重繼承來深化理解:code

class A:
    def ping(self):
        print("A-ping:",self)
class B(A):
    def pong(self):
        print("B-pong:",self)
class C(A):
    def pong(self):
        print("C-PONG:",self)
class D(B, C):
    def ping(self):
        print("D-ping:",self)
        super().ping()
    def pingpong(self):
        self.ping()
        super().ping()
        self.pong()
        super(B,D).pong(self)
d=D()
d.pingpong()
print(D.mro())

輸出以下:

D-ping: <__main__.D object at 0x000001B77096EAC8>
A-ping: <__main__.D object at 0x000001B77096EAC8>#前兩行對應self.ping()。
A-ping: <__main__.D object at 0x000001B77096EAC8>#此處super調用父類的ping方法。
B-pong: <__main__.D object at 0x000001B77096EAC8>
C-PONG: <__main__.D object at 0x000001B77096EAC8>#此處從B以後搜索父類的pong()
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]#類D的__mro__,數據以元組的形式存儲。

分析:d.pingpong()執行super.ping(),super按照MRO查找父類的ping方法,查詢在類B到ping以後輸出了B.ping()。

三、處理多重繼承的建議

(1)把接口繼承和實現繼承區分開;

  • 繼承接口:建立子類型,是框架的支柱;
  • 繼承實現:經過重用避免代碼重複,一般能夠換用組合和委託模式。

(2)使用抽象基類顯式表示接口;
(3)經過混入重用代碼;
混入類爲多個不相關的子類提供方法實現,便於重用,但不會實例化。而且具體類不能只繼承混入類。
(4)在名稱中明確指明混入;
Python中沒有把類聲明爲混入的正規方式,Luciano推薦在名稱中加入Mixin後綴。如Tkinter中的XView應變成XViewMixin。
(5)抽象基類能夠做爲混入,反過來則不成立;
抽象基類與混入的異同:

  • 抽象基類會定義類型,混入作不到;
  • 抽象基類能夠做爲其餘類的惟一基類,混入作不到;
  • 抽象基類實現的具體方法只能與抽象基類及其超類中的方法協做,混入沒有這個侷限。

(6)不要子類化多個具體類;
具體類能夠沒有,或者至多一個具體超類。
例如,Class Dish(China,Japan,Tofu)中,若是Tofu是具體類,那麼China和Japan必須是抽象基類或混入。
(7)爲用戶提供聚合類;
聚合類是指一個類的結構主要繼承自混入,自身沒有添加結構或行爲。Tkinter採納了此條建議。
(8)優先使用對象組合,而不是類繼承。
優先使用組合能夠令設計更靈活。
組合和委託能夠代替混入,但不能取代接口繼承去定義類型層次結構。

注:super調用知識引自
做者: mozillazg
連接:https://segmentfault.com/a/11...

相關文章
相關標籤/搜索