摘要:本文重點不要試圖在內置類型的子類中重寫方法,可以繼承的可拓展類尋求變通掌握多重繼承中的和了解處理多重繼承的一些建議。子類化的代碼如下輸出小結上述問題只發生在語言實現的內置類型子類化情況中,而且只影響直接繼承內置類型的自定義類。
導語:本文章記錄了本人在學習Python基礎之面向對象篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習并交流。
本文重點:
1、不要試圖在內置類型的子類中重寫方法,可以繼承collections的可拓展類尋求變通;一、子類化內置類型的缺點 1、內置類型的方法不會調用子類覆蓋的方法
2、掌握多重繼承中的MRO和Super;
3、了解處理多重繼承的一些建議。
內置類可以子類化,但是內置類型的方法不會調用子類覆蓋的方法。下面以繼承dict的自定義子類重寫__setitem__為例說明:
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模塊,如UserDict,UserList,UserString。這些類做了特殊設計,因此易于拓展。子類化UserDict的代碼如下:
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就不會有此問題。
二、多重繼承 1、方法解析順序(Method Resolution Order,MRO)在多重繼承中存在不相關的祖先類實現同名方法引起的沖突問題,這種問題稱作“菱形問題”。Python依靠特定的順序遍歷繼承圖,這個順序叫做方法解析順序。如圖,左圖是類的UML圖,右圖中的虛線箭頭是方法解析順序。
提到類的屬性__mro__,就會提到super:
super 是個類,既不是關鍵字也不是函數等其他數據結構。
作用:super是子類用來調用父類方法的。
語法:super(a_type, obj);
a_type是obj的__mro__,當然也可以是__mro__的一部分,同時issubclass(obj,a_type)==true
舉個例子, 有個 MRO: [A, B, C, D, E, object]
我們這樣調用:super(C, A).foo()
super 只會從 C 之后查找,即: 只會在 D 或 E 或 object 中查找 foo 方法。
下面構造一個菱形問題的多重繼承來深化理解:
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() [, , , , ]#類D的__mro__,數據以元組的形式存儲。
分析:d.pingpong()執行super.ping(),super按照MRO查找父類的ping方法,查詢在類B到ping之后輸出了B.ping()。
3、處理多重繼承的建議(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...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/41331.html
摘要:自己定義的抽象基類要繼承。抽象基類可以包含具體方法。這里想表達的觀點是我們可以偷懶,直接從抽象基類中繼承不是那么理想的具體方法。 抽象基類 抽象基類的常見用途: 實現接口時作為超類使用。 然后,說明抽象基類如何檢查具體子類是否符合接口定義,以及如何使用注冊機制聲明一個類實現了某個接口,而不進行子類化操作。 如何讓抽象基類自動識別任何符合接口的類——不進行子類化或注冊。 接口在動態類...
摘要:組合繼承前面兩種模式的特點類式繼承通過子類的原型對父類實例化實現的,構造函數式繼承是通過在子類的構造函數作用環境中執行一次父類的構造函數來實現的。 類有三部分 構造函數內的,供實例化對象復制用的 構造函數外的,直接通過點語法添加的,供類使用,實例化對象訪問不到 類的原型中的,實例化對象可以通過其原型鏈間接地訪問到,也是供所有實例化對象所共用的。 類式繼承 類的原型對象的作用就是為類...
摘要:先來一張圖看看幾個名詞的關系構造函數原型實例原諒我的狂草字體,我手寫比用電腦畫快。今天我們只說原型鏈,所以接下來我就圍繞著原型鏈的幾個部分說起。每個函數都有一個屬性借用屬性存儲了的原型對象。 先來一張圖看看幾個名詞的關系 構造函數、原型、實例 showImg(https://segmentfault.com/img/remote/1460000018194082); 原諒我的狂草字體,...
閱讀 3738·2021-10-15 09:42
閱讀 2600·2021-09-03 10:50
閱讀 1632·2021-09-03 10:28
閱讀 1792·2019-08-30 15:54
閱讀 2515·2019-08-30 12:46
閱讀 408·2019-08-30 11:06
閱讀 2823·2019-08-30 10:54
閱讀 527·2019-08-29 12:59