歡迎光臨
每天分享高質量文章

Python 繼承概念的這些優缺點你知道嗎?

學Python最簡單的方法是什麼?推薦閱讀:Python開發工程師成長魔法


作為一名程式員或者準程式員,對於面向物件程式設計簡直熟悉的不能再熟悉。作為當今最流行的程式設計思想之一(或許可以去掉“之一”),無論是在面試還是工作中,面向物件都是無法避開的話題。

對於Python程式員來說,OOP(面向物件程式設計)的三大特性——資料封裝、繼承和多型通常是面試中的重點考察問題,因此大部分人對此也相當熟悉。

不過,OOP的優缺點你真的瞭解嗎?今天這篇文章會帶領大家瞭解一下三大特點中繼承的優缺點。

OOP()即所謂面向物件程式設計,是一種程式設計思想。OOP把物件作為程式的基本單元,一個物件包含了資料和運算元據的函式。面向物件的程式設計把計算機程式視為一組物件的集合,而每個物件都可以接收其他物件發過來的訊息,並處理這些訊息,計算機程式的執行就是一系列訊息在各個物件之間傳遞。

面向物件最重要的概念就是類(Class)和實體(Instance),必須牢記類是抽象的模板,而實體是根據類創建出來的一個個具體的“物件”,每個物件都擁有相同的方法,但各自的資料可能不同。

假設我們要建立一個Student類,在Python中,定義類是透過class關鍵字:

class後面緊接著是類名,即Student,類名通常是大寫開頭的單詞,緊接著是(object),表示該類是從哪個類繼承下來的,繼承的概念我們後面再講,通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。

定義好了Student類,就可以根據Student類創建出Student的實體,建立實體是透過類名+()實現的:

可以看到,變數bart指向的就是一個Student的實體,後面的0x10a67a590是記憶體地址,每個object的地址都不一樣,而Student本身則是一個類。

可以自由地給一個實體變數系結屬性,比如,給實體bart系結一個name屬性:

由於類可以起到模板的作用,因此,可以在建立實體的時候,把一些我們認為必須系結的屬性強制填寫進去。透過定義一個特殊的__init__方法,在建立實體的時候,就把name,score等屬性綁上去:

註意:特殊方法“__init__”前後分別有兩個下劃線!!!

註意到__init__方法的第一個引數永遠是self,表示建立的實體本身,因此,在__init__方法內部,就可以把各種屬性系結到self,因為self就指向建立的實體本身。

有了__init__方法,在建立實體的時候,就不能傳入空的引數了,必須傳入與__init__方法匹配的引數,但self不需要傳,Python直譯器自己會把實體變數傳進去:

和普通的函式相比,在類中定義的函式只有一點不同,就是第一個引數永遠是實體變數self,並且,呼叫時,不用傳遞該引數。除此之外,類的方法和普通函式沒有什麼區別,所以,你仍然可以用預設引數、可變引數、關鍵字引數和命名關鍵字引數。

繼承

什麼是繼承?

繼承是一種建立類的方法,在python中,一個類可以繼承來自一個或多個父類。原始類稱為基類或超類。

檢視繼承:

什麼時候用繼承?

假如已經有幾個類,而類與類之間有共同的變數屬性和函式屬性,那就可以把這幾個變數屬性和函式屬性提取出來作為基類的屬性。而特殊的變數屬性和函式屬性,則在本類中定義,這樣只需要繼承這個基類,就可以訪問基類的變數屬性和函式屬性。可以提高程式碼的可擴充套件性。

繼承和抽象(先抽象再繼承)

抽象即提取類似的部分。基類就是抽象多個類共同的屬性得到的一個類。

Garen類和Riven類都有nickname、aggressivity、life_value、script四個變數屬性和attack()函式屬性,這裡可以抽象出一個Hero類,裡面有裡麵包含這些屬性。

嚴格來說,上述Hero.init(self,…),不能算作子類呼叫父類的方法。因為我們如果去掉(Hero)這個繼承關係,程式碼仍能得到預期的結果。

總結python中繼承的特點:


  1. 在子類中,並不會自動呼叫基類的init(),需要在派生類中手動呼叫。

  2. 在呼叫基類的方法時,需要加上基類的類名字首,且需要帶上self引數變數。

  3. 先在本類中查詢呼叫的方法,找不到才去基類中找。

繼承的優缺點探討

子類化內建型別的缺點

1. 內建型別的方法不會呼叫子類改寫的方法

內建類可以子類化,但是內建型別的方法不會呼叫子類改寫的方法。下麵以繼承dict的自定義子類重寫__setitem__為例說明:

從輸出可以看到,鍵值對one=1和three=3存入a時均呼叫了dict的__setitem__,只有[]運運算元會呼叫我們預先改寫的方法。


問題的解決方式在於不去子類化dict,而是子類化colections.UserDict。

2、子類化collections中的類

使用者自定義的類應該繼承collections模組,如UserDict,UserList,UserString。這些類做了特殊設計,因此易於拓展。子類化UserDict的程式碼如下:

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

多重繼承

1. 方法解析順序(Method Resolution Order,MRO)

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

2、super

提到類的屬性__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 方法。

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

輸出如下:

分析: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)優先使用物件組合,而不是類繼承。
優先使用組合可以令設計更靈活。
組合和委託可以代替混入,但不能取代介面繼承去定義型別層次結構。



————近期開班————

《馬哥教育Python自動化開發全能實戰班》是馬哥教育聯合阿裡、豆瓣、大眾點評等一線Python工程師,根據目前企業需求的Python開發人才進行了深度定製,加入大量一線網際網路公司:大眾點評、餓了麼、騰訊等生產環境真是專案,課程由淺入深,從Python基礎到Python高階,讓你融匯貫通Python基礎理論到日誌分析、todolist任務管理系統、類Flask框架多人部落格、CMDB資產管理、任務排程系統、運維流程系統六大專案實戰,手把手教學讓你從0開始蛻變成Hold住年薪30萬的Python自動化開發人才。

10期面授班:2018年03月05號(北京)

11期網路班:2018年03月17號網路

更多Python好文請點選【閱讀原文】哦

↓↓↓

贊(0)

分享創造快樂