目錄
Python的super()函數概述
單繼承中的super()
super()能為你做什麼?
深入super()
多重繼承中的super()
多重繼承概述
方法解析順序
多重繼承替代方案
super()回顧
雖然Python不是一種純面向對象的語言,但它足夠靈活,功能強大,足以讓您使用面向對象的範例構建應用程式。 Python實現這一目標的方法之一是通過使用super()來支持繼承。
在本教程中,您將了解以下內容:
Python中的繼承概念
Python中的多重繼承
super()函數如何工作
單繼承中的super()函數如何工作
多繼承中的super()函數如何工作
Python的super()函數概述
如果您有使用面向對象語言的經驗,那麼您可能已經熟悉了super()的功能。
如果沒有,不要害怕!雖然官方文檔是相當技術性的,但在高級別上,super()允許繼承超類的子類訪問該超類中的方法。
super()單獨返回超類的臨時對象,然後允許您調用該超類的方法。
你為什麼要這樣做呢? 雖然其可能性超出了你的想像,但常見的用例是構建子類來擴展先前已構建的類的功能。
使用super()調用以前構建的方法可以使您無需在子類中重寫這些方法,並允許您使用最少的代碼更改來替換超類。
單繼承中的super()
如果您不熟悉面向對象的編程概念,繼承可能是一個不熟悉的術語。 繼承是面向對象編程中的一個概念,其中類從另一個類派生(或繼承)屬性和行為,而無需再次實現它們。
至少對我來說,在查看代碼時更容易理解這些概念,所以讓我們編寫描述繼承結構的類:
這裡有兩個相似的類:Rectangle和Square。
你可以使用它們如下:
在此示例中,您有兩個彼此相關的形狀:正方形是一種特殊的矩形。 但是,代碼並不反映這種關係,因此具有基本上重複的代碼。
通過使用繼承,您可以減少寫入的代碼量,同時反映矩形和正方形之間的真實世界關係:
在這裡,您使用了super()來調用Rectangle類的__init __(),允許您在Square類中使用它而不重複代碼。 如下所示,核心功能在進行更改後仍然存在:
在此示例中,Rectangle是超類,Square是子類。
因為Square和Rectangle .__ init __()方法非常相似,所以你可以使用super()從Square的方法中調用超類的.__ init __()方法(Rectangle .__ init __())。 這裡設置了.length和.width屬性,即使您只需要為Square構造函數提供單個長度參數。
當你運行它時,即使你的Square類沒有顯式地實現它,對.area()的調用將使用超類中的.area()方法並列印16. Square類從Rectangle繼承.area() 方法。
注意:要了解有關Python中繼承和面向對象概念的更多信息,請務必在Python 3中查看面向對象編程(OOP)。
super()能為你做什麼?
那麼super()在單繼承中能為你做什麼呢?
與其他面向對象語言一樣,它允許您在子類中調用超類的方法。 這種情況的主要用例是擴展繼承方法的功能。
在下面的示例中,您將創建一個繼承自Square的類Cube,並擴展.area()的功能(通過Square繼承自Rectangle類)以計算Cube實例的表面積和體積:
現在您已經構建了類,讓我們看一下邊長為3的立方體的表面積和體積:
注意:請注意,在我們上面的示例中,單獨的super()不會為您調用方法:您必須在代理對象本身上調用該方法。
在這裡,您已經為Cube類實現了兩種方法:.surface_area()和.volume()。這兩種計算都依賴於計算單個面的面積,因此您不必重新實現面積計算,而是使用super()來擴展面積計算。
另請注意,Cube類定義沒有.__ init __()。因為Cube繼承自Square,而.__ init __()並沒有為Cube做任何不同的事情,所以你可以跳過定義它,並且將自動調用超類(Square)的.__ init __()。
super()將委託對象返回給父類,因此您可以直接調用它所需的方法:super().area()。
這不僅使我們不必重寫面積計算方法,而且還允許我們在一個位置更改內部.area()邏輯。當你有一些繼承自一個超類的子類時,這尤其有用。
深入super()
在進入多重繼承之前,讓我們快速介紹一下super()的機制。
雖然上面(和下面)的示例在沒有任何參數的情況下調用super(),但super()也可以使用兩個參數:第一個是子類,第二個參數是作為該子類實例的對象。
首先,讓我們看兩個示例,通過使用已有的類來展示操作第一個變量可以做什麼:
在Python 3中,super(Square,self)調用等同於無參數的super()調用。 第一個參數指的是子類Square,而第二個參數指的是Square對象,在這種情況下,它是self。 您也可以使用其他類調用super():
在此示例中,您將Square設置為super()的子類參數,而不是Cube。 這導致super()開始在實例層次結構中的Square上方的一個級別搜索匹配方法(在本例中為.area()方法),在本例中為Rectangle類。
在此特定示例中,行為不會更改。 但想像一下Square還實現了一個你想要確保Cube不使用的.area()方法。 以這種方式調用super()可以讓你這樣做。
注意:雖然為了探索它在內部的工作方式,我們正在對super()的參數進行大量的調整,但要注意不要經常這樣做。
對於大多數用例,建議使用對super()的無參數調用,並且需要定期更改搜索層次結構可能表明更大的設計問題。
第二個參數怎麼樣?請記住,這是一個對象,它是用作第一個參數的類的實例。例如,isinstance(Cube,Square)必須返回True。
通過包含實例化對象,super()返回一個綁定方法:綁定到對象的方法,用來為方法提供對象的上下文,例如任何實例屬性。如果未包含此參數,則返回的方法只是一個函數,與對象的上下文無關。
有關綁定方法,未綁定方法和函數的更多信息,請閱讀其描述符系統上的Python文檔。
注意:從技術上講,super()不返回方法。它返回一個代理對象。這是一個對象,它將調用正確的類方法,而不需要另外創建一個對象。
多重繼承中的super()
現在您已經看過了單繼承的概述以及super()和單繼承的一些示例,您將進一步了解多重繼承的概述和一些示例,這些示例將演示多繼承如何工作以及super()如何啟用該功能。
多重繼承概述
還有另一個用例,其中super()確實有用,而且這個用例並不像單個繼承場景那樣常見。 除了單繼承之外,Python還支持多繼承,其中子類可以從多個不必繼承的超類(也稱為兄弟類)繼承。
我是一個非常直觀的人,我發現圖表對於理解這樣的概念非常有幫助。 下圖顯示了一個非常簡單的多繼承方案,其中一個類繼承自兩個不相關(兄弟)的超類:
為了更好地說明多重繼承的實際應用,下面是一些代碼供您動手實踐,展示如何從三角形和正方形構建一個右金字塔(帶有方形底座的金字塔):
注意:術語「傾斜高度」可能不熟悉,特別是如果你已經使用幾何類或編寫金字塔類已經有一段時間了。
傾斜高度是從物體底部中心(如金字塔)到其面部到該物體頂部的高度。您可以在WolframMathWorld上閱讀更多關於傾斜高度的信息。
此示例聲明了一個Triangle類和一個繼承Square和Triangle的RightPyramid類。
您將看到另一個使用super()的.area()方法,就像在單繼承中一樣,目的是用到在Rectangle類中一直定義的.perimeter()和.area()方法。
注意:您可能會注意到上面的代碼尚未使用Triangle類中的任何繼承屬性。後面的例子將充分利用Triangle和Square的繼承。
但問題是兩個超類(Triangle和Square)都定義了一個.area()。花一點時間思考在RightPyramid上調用.area()時會發生什麼,然後嘗試調用它,如下所示:
您是否猜測Python會嘗試調用Triangle.area()? 這是因為所謂的方法解析順序在起作用。
注意:我們怎麼注意到Triangle.area()被調用了,而不是像我們希望的那樣,Square.area()? 如果查看回溯的最後一行(在AttributeError之前),您將看到對特定代碼行的引用:
您可以將幾何類中的這個方法認為是三角形面積公式。 或者,如果你像我一樣,你可能已經滾動到Triangle和Rectangle類定義,並在Triangle.area()中看到了相同的代碼。
方法解析順序
方法解析順序(或MRO)告訴Python如何搜索繼承的方法。 當你使用super()時,這會派上用場,因為MRO會告訴你Python將使用super()以及以什麼順序調用的方法。
每個類都有一個.__ mro__屬性,允許我們檢查順序,所以讓我們這樣做:
這告訴我們首先在Rightpyramid中搜索方法,然後在Triangle中搜索,然後在Square中搜索,然後在Rectangle中搜索,然後,如果沒有找到,則在對象中搜索。
這裡的問題是解釋器在Square和Rectangle之前在Triangle中搜索.area(),並且在Triangle中找到.area()時,Python會調用它而不是你想要的那個。 因為Triangle.area()期望有.height和.base屬性,所以Python會拋出AttributeError。
幸運的是,您可以控制MRO的構建方式。 只需更改RightPyramid類的籤名,即可按所需順序進行搜索,方法將正確解析:
請注意,RightPyramid使用Square類中的.__ init __()進行部分初始化。 這允許.area()在對象上使用.length,如設計的那樣。
現在,您可以構建金字塔類,檢查MRO並計算表面積:
您可以看到MRO現在是您所期望的,並且您也可以檢查金字塔的表面積,這要歸功於.area()和.perimeter()。
不過,這裡仍然存在問題。為了簡單起見,我在這個例子中故意設置了一些錯誤:第一個,可以說最重要的是,我有兩個具有相同方法名稱和籤名的獨立類。
這會導致方法解析問題,因為將調用MRO列表中遇到的.area()的第一個實例。
當您使用具有多重繼承的super()時,必須設計您的類以進行協作。其中一點是確保您的方法是唯一的,以便通過籤名確保方法是唯一的 - 無論是使用方法名稱還是方法參數,在MRO中正確解析它們。
在這種情況下,為了避免對代碼進行徹底檢查,可以將Triangle類的.area()方法重命名為.tri_area()。這樣,area方法可以繼續使用類屬性而不是使用外部參數:
讓我們繼續在RightPyramid類中使用它:
這裡的下一個問題是代碼沒有像Square對象那樣的委託的Triangle對象,所以調用.area_2()會給我們一個AttributeError,因為.base和.height沒有任何值。
你需要做兩件事來解決這個問題:
使用super()調用的所有方法都需要調用其超類的該方法版本。 這意味著您需要將super().__ init __()添加到Triangle和Rectangle的.__ init __()方法中。
重新設計所有.__ init __()調用以獲取關鍵字字典。 請參閱下面的完整代碼。
此代碼中存在許多重要差異:
kwargs在某些地方被修改(例如RightPyramid .__ init __()): 這將允許這些對象的用戶僅使用對該特定對象有意義的參數來實例化它們。
在**kwargs之前設置命名參數:你可以在RightPyramid .__ init __()中看到這個。 這具有從**kwargs字典中去除特定鍵的簡潔效果,因此當它在MRO中最終進行到object時,**kwargs為空。
注意:跟蹤kwargs的狀態在這裡可能很棘手,所以這裡是一個.__ init __()調用表,顯示擁有該調用的類,以及該調用期間kwargs的內容:
現在,當您使用這些更新的類時,您有:
起作用了! 您已經使用super()成功追溯複雜的類層次結構,同時使用繼承和組合來創建具有最少重新實現的新類。
多重繼承替代方案
如您所見,多重繼承可能很有用,但也會導致非常複雜的情況和難以閱讀的代碼。 擁有整齊地從多個其他對象繼承所有東西的對象也很少見。
如果您發現自己開始使用多重繼承和複雜的類層次結構,那麼值得問問自己是否可以通過使用組合而不是繼承來實現更清晰,更易於理解的代碼。
通過組合,您可以從一個稱為mixin的專用簡單類中為您的類添加非常特定的功能。
由於本文主要關注繼承,因此我不會詳細介紹組合以及如何在Python中使用它,但這裡有一個使用VolumeMixin為我們的3D對象提供特定功能的簡短示例 - 在這種情況下,是一個體積計算:
在這個例子中,代碼被重新設計為包含一個名為VolumeMixin的mixin。 然後,Cube使用mixin並使Cube能夠計算其體積,如下所示:
這個mixin可以在任何需要計算體積的類中以相同的方式使用,並且公式area*height返回正確的結果。
super()回顧
在本教程中,您學習了如何使用super()為類增加繼承。 您的學習之旅從單繼承的回顧開始,然後展示了如何使用super()輕鬆調用超類方法。
然後,您了解了多重繼承如何在Python中工作,以及將super()與多重繼承相結合的技術。您還了解了Python如何使用方法解析順序(MRO)解析方法調用,以及如何檢查和修改MRO以確保在適當的時間調用適當的方法。
有關Python中面向對象編程和使用super()的更多信息,請查看以下資源:
英文原文:https://realpython.com/python-super/