使用Python super()為您的類增強

使用Python super()為您的類增強

雖然Python不僅僅是一種面向對象的語言,但它足夠靈活,功能強大,足以讓您使用面向對象的範例構建應用程序。Python實現這一目標的方法之一是支持繼承,它與之相關super()。

在本教程中,您將瞭解以下內容:

  • Python中的繼承概念
  • Python中的多重繼承
  • super()功能如何工作
  • super()單繼承中的函數如何工作
  • super()多繼承中的函數如何工作

免費獎勵: 關於Python掌握的5個想法,Python開發人員的免費課程,向您展示將Python技能提升到新水平所需的路線圖和思維模式。

Python super()函數概述

如果您有使用面嚮對象語言的經驗,那麼您可能已經熟悉了它的功能super()。

如果沒有,不要害怕!雖然官方文檔是相當技術性的,但是在高級別上super(),您可以從繼承自它的子類訪問超類中的方法。

super() 單獨返回超類的臨時對象,然後允許您調用該超類的方法。

你為什麼要這樣做呢?雖然可能性受到您的想象力的限制,但常見的用例是構建擴展先前構建的類的功能的類。

調用以前構建的方法super()可以使您無需在子類中重寫這些方法,並允許您使用最少的代碼更改來交換超類。

super() 在單一繼承中

如果您不熟悉面向對象的編程概念,繼承可能是一個不熟悉的術語。繼承是面向對象編程中的一個概念,其中類從另一個類派生(或繼承)屬性和行為,而無需再次實現它們。

至少對我來說,在查看代碼時更容易理解這些概念,所以讓我們編寫描述某些形狀的類:

class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * self.length + 2 * self.width
class Square:
def __init__(self, length):
self.length = length
def area(self):
return self.length * self.length
def perimeter(self):
return 4 * self.length

這裡有兩個相似的類:Rectangle和Square。

你可以使用它們如下:

>>>

>>> square = Square(4)

>>> square.area()

16

>>> rectangle = Rectangle(2,4)

>>> rectangle.area()

8

在此示例中,您有兩個彼此相關的形狀:正方形是一種特殊的矩形。但是,代碼並不反映這種關係,因此具有基本上重複的代碼。

通過使用繼承,您可以減少寫入的代碼量,同時反映矩形和正方形之間的真實世界關係:

class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * self.length + 2 * self.width
# Here we declare that the Square class inherits from the Rectangle class
class Square(Rectangle):
def __init__(self, length):
super().__init__(length, length)

在這裡,你已經使用super()調用__init__()的的Rectangle類,允許你使用它的Square類不重複的代碼。下面,核心功能在進行更改後仍然存在:

>>>

>>> square = Square(4)
>>> square.area()
16

在這個例子中,Rectangle是超類,並且Square是子類。

因為Square和Rectangle .__init__()方法非常相似,所以你可以簡單地通過使用來調用超類的.__init__()方法(Rectangle.__init__())。即使您只需要為構造函數提供單個參數,也會設置和屬性。Squaresuper().length.widthlengthSquare

當你運行它時,即使你的Square類沒有顯式實現它,調用.area()將使用.area()超類中的方法並打印16。在Square類繼承 .area()自Rectangle類。

注意:

要了解有關Python中繼承和麵向對象概念的更多信息,請務必在Python 3中查看面向對象編程(OOP)。

能super()為你做什麼?

那麼super()單繼承可以為你做什麼呢?

與其他面嚮對象語言一樣,它允許您在子類中調用超類的方法。這種情況的主要用例是擴展繼承方法的功能。

在下面的示例中,您將創建一個Cube繼承Square並擴展.area()(從Rectangle類繼承Square)的功能的類,以計算Cube實例的表面積和體積:

class Square(Rectangle):
def __init__(self, length):
super().__init__(length, length)
class Cube(Square):
def surface_area(self):
face_area = super().area()
return face_area * 6
def volume(self):
face_area = super().area()
return face_area * self.length

現在您已經構建了類,讓我們看一下邊長為的立方體的表面積和體積3:

>>>

>>> cube = Cube(3)
>>> cube.surface_area()
54
>>> cube.volume()

27

注意:請注意,在上面的示例中,super()單獨不會為您調用方法:您必須在代理對象本身上調用該方法。

在這裡,您已經為Cube該類實現了兩種方法:.surface_area()和.volume()。這兩種計算都依賴於計算單個面的面積,因此您不必重新實現面積計算,而是使用super()擴展面積計算。

另請注意,Cube類定義沒有.__init__()。因為Cube從繼承Square和.__init__()並沒有真正做什麼不同的Cube比它已經這樣做了Square,你可以跳過定義它,和.__init__()超(中Square)將被自動調用。

super()將委託對象返回給父類,因此您可以直接調用它所需的方法:super().area()。

這不僅使我們不必重寫區域計算,而且還允許我們.area()在單個位置更改內部邏輯。當你有一些繼承自一個超類的子類時,這尤其有用。

一個super()深潛

在進行多重繼承之前,讓我們快速介紹一下它的機制super()。

雖然上面(和下面)的示例在super()沒有任何參數的情況下調用,super()但也可以採用兩個參數:第一個是子類,第二個參數是作為該子類實例的對象。

首先,讓我們看兩個示例,說明使用已經顯示的類操作第一個變量可以做什麼:

class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * self.length + 2 * self.width
class Square(Rectangle):
def __init__(self, length):
super(Square, self).__init__(self, length, length)

在Python 3中,super(Square, self)調用等同於無參數super()調用。第一個參數引用子類Square,而第二個參數引用一個Square對象,在這種情況下,該對象是self。您也可以super()與其他課程一起打電話:

class Cube(Square):
def surface_area(self):
face_area = super(Square, self).area()
return face_area * 6
def volume(self):
face_area = super(Square, self).area()
return face_area * self.length

在此示例中,您將設置Square為子類參數super(),而不是Cube。這導致super()在這種情況下開始在實例層次結構中的上.area()一級搜索匹配方法(在這種情況下)。SquareRectangle

在此特定示例中,行為不會更改。但想象一下,Square還實現了一個.area()你想確保Cube不使用的功能。super()以這種方式呼叫可以讓你這樣做。

警告:雖然我們正在大量擺弄這些參數super(),以便探討它是如何工作的,但我要小心不要經常這樣做。

super()對於大多數用例,建議使用無參數調用,並且需要定期更改搜索層次結構可能表示更大的設計問題。

第二個參數怎麼樣?請記住,這是一個對象,它是用作第一個參數的類的實例。例如,isinstance(Cube, Square)必須返回True。

通過包含實例化對象,super()返回綁定方法:綁定到對象的方法,該方法為方法提供對象的上下文,例如任何實例屬性。如果未包含此參數,則返回的方法只是一個函數,與對象的上下文無關。

有關綁定方法,未綁定方法和函數的更多信息,請閱讀其描述符系統上的Python文檔。

注意:從技術上講,super()不會返回方法。它返回一個代理對象。這是一個對象,它將調用委託給正確的類方法,而不需要另外創建一個對象。

super() 在多重繼承中

既然您已經完成了概述以及一些super()單繼承的示例,那麼您將會看到一個概述和一些示例,它們將演示多繼承如何工作以及如何super()啟用該功能。

多重繼承概述

還有另一個用例super()非常閃耀,而且這個用例並不像單個繼承場景那樣常見。除了單繼承之外,Python還支持多繼承,其中子類可以從多個不必繼承的超類(也稱為兄弟類)繼承。

我是一個非常直觀的人,我發現圖表對於理解這樣的概念非常有幫助。下圖顯示了一個非常簡單的多繼承方案,其中一個類繼承自兩個不相關(兄弟)的超類:

使用Python super()為您的類增強

多重繼承的圖解示例(圖片來源:Kyle Stratis)

為了更好地說明多重繼承的實際應用,下面是一些代碼供您試用,展示如何在a Triangle和a中構建一個正確的金字塔(一個帶方形底座的金字塔)Square:

class Triangle:
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
class RightPyramid(Triangle, Square):
def __init__(self, base, slant_height):
self.base = base
self.slant_height = slant_height
def area(self):
base_area = super().area()
perimeter = super().perimeter()
return 0.5 * perimeter * self.slant_height + base_area

注意:術語傾斜高度可能不熟悉,特別是如果你已經使用幾何類或在任何金字塔上工作已經有一段時間了。

傾斜高度是從物體底部中心(如金字塔)到其面部到該物體頂部的高度。您可以在WolframMathWorld上閱讀更多關於傾斜高度的信息。

這個例子聲明一個Triangle類和RightPyramid來自兩個繼承類Square和Triangle。

你會看到另一個.area()使用方法,super()就像在單繼承,與它的目的達到.perimeter()和.area()方法定義了在一路Rectangle類。

注意:您可能會注意到上面的代碼尚未使用Triangle該類中的任何繼承屬性。後面的例子將充分利用繼承來自兩個Triangle和Square。

但問題是兩個超類(Triangle和Square)都定義了一個.area()。花一秒鐘想想,當你調用會發生什麼事情.area()上RightPyramid,然後嘗試調用它象下面這樣:

>>>

>> pyramid = RightPyramid(2, 4)

>> pyramid.area()

Traceback (most recent call last):

File "shapes.py", line 63, in <module>

print(pyramid.area())

File "shapes.py", line 47, in area

base_area = super().area()

File "shapes.py", line 38, in area

return 0.5 * self.base * self.height

AttributeError: 'RightPyramid' object has no attribute 'height'

你猜想Python會試著打電話Triangle.area()嗎?這是因為稱為方法解析順序的東西。

注意:我們Triangle.area()是如何注意到的那樣,而不是像我們希望的那樣Square.area()?如果你查看回溯的最後一行(在之前AttributeError),你會看到對特定代碼行的引用:

返回 0.5 * 自我。基地 * 自我。高度

您可以將幾何類中的這個識別為三角形區域的公式。否則,如果你像我一樣,你可能已經滾動到Triangle與Rectangle類的定義和出現在相同的代碼Triangle.area()。

方法解決順序

方法解析順序(或MRO)告訴Python如何搜索繼承的方法。這在您使用時會派上用場,super()因為MRO會告訴您Python將在何處查找您正在調用的方法super()以及以何種順序查找。

每個類都有一個.__mro__允許我們檢查順序的屬性,所以讓我們這樣做:

>>>

>>> RightPyramid 。__mro 
__(<class'__main __。RightPyramid'>,<class'__ main __。Triangle'>,
<class'__ main __。Square'>,<class'__ main __。Rectangle'>,
< class'object '>)

這告訴我們首先搜索方法Rightpyramid,然後是in Triangle,然後是in Square,Rectangle然後,如果沒有找到object任何類,則從中查找所有類。

這裡的問題是,該解釋正在尋找.area()在Triangle之前Square和Rectangle,並在發現.area()中Triangle,Python會調用它,而不是你想要的。因為Triangle.area()期望有一個.height和一個.base屬性,Python會拋出一個AttributeError。

幸運的是,您可以控制MRO的構建方式。只需更改RightPyramid類的簽名,即可按所需順序進行搜索,方法將正確解析:

class RightPyramid(Square, Triangle):

def __init__(self, base, slant_height):

self.base = base

self.slant_height = slant_height

super().__init__(self.base)

def area(self):

base_area = super().area()

perimeter = super().perimeter()

return 0.5 * perimeter * self.slant_height + base_area

請注意,RightPyramid與部分初始化.__init__()從Square類。這允許.area()按照設計使用.length對象。

現在,您可以構建金字塔,檢查MRO並計算表面積:

>>>

>>> pyramid = RightPyramid(2, 4)
>>> RightPyramid.__mro__
(<class '__main__.RightPyramid'>, <class '__main__.Square'>,
<class '__main__.Rectangle'>, <class '__main__.Triangle'>,
<class 'object'>)
>>> pyramid.area()
20.0

你看到MRO現在是你所期望的,你也可以檢查金字塔的區域,感謝.area()和.perimeter()。

不過,這裡仍然存在問題。為了簡單起見,我在這個例子中做了一些錯誤:第一個,可以說最重要的是,我有兩個具有相同方法名稱和簽名的獨立類。

這會導致方法解決問題,因為.area()將調用MRO列表中遇到的第一個實例。

當您使用super()多重繼承時,必須設計您的類以進行合作。部分原因是確保您的方法是唯一的,以便通過確保方法簽名是唯一的 - 無論是使用方法名稱還是方法參數,在MRO中解析它們。

在這種情況下,為了避免對代碼進行徹底檢查,可以將Triangle類的.area()方法重命名為.tri_area()。這樣,area方法可以繼續使用類屬性而不是使用外部參數:

class Triangle:

def __init__(self, base, height):

self.base = base

self.height = height

super().__init__()

def tri_area(self):

return 0.5 * self.base * self.height

我們也繼續在RightPyramid課堂上使用它:

class RightPyramid(Square, Triangle):
def __init__(self, base, slant_height):
self.base = base
self.slant_height = slant_height
super().__init__(self.base)
def area(self):
base_area = super().area()
perimeter = super().perimeter()
return 0.5 * perimeter * self.slant_height + base_area
def area_2(self):
base_area = super().area()
triangle_area = super().tri_area()
return triangle_area * 4 + base_area

這裡的一個問題是,該代碼不具有委託Triangle對象像它的Square對象,所以打電話.area_2()給我們的AttributeError,因為.base並.height沒有任何價值。

你需要做兩件事來解決這個問題:

  1. 調用的所有方法都super()需要調用其超類的該方法版本。這意味著你將需要添加super().__init__()的.__init__()方法Triangle和Rectangle。
  2. 重新設計所有.__init__()調用以獲取關鍵字字典。請參閱下面的完整代碼。

完整的代碼示例

顯示隱藏

此代碼中存在許多重要差異:

  • kwargs在某些地方(例如RightPyramid.__init__())修改:這將允許這些對象的用戶僅使用對該特定對象有意義的參數來實例化它們。
  • 之前設置命名參數**kwargs:您可以在中查看RightPyramid.__init__()。這具有從**kwargs字典中彈出該鍵的簡潔效果,因此當它在object類中的MRO結束時,它**kwargs是空的。

注意:kwargs這裡的狀態可能很棘手,所以這裡是一個.__init__()按順序調用的表,顯示擁有該調用的類,以及該kwargs調用期間的內容:

類命名參數kwargsRightPyramidbase, slant_heightSquarelengthbase, heightRectanglelength, widthbase, heightTrianglebase, height

現在,當您使用這些更新的類時,您有:

>>>

>>> pyramid = RightPyramid(base=2, slant_height=4)

>>> pyramid.area()

20.0

>>> pyramid.area_2()

20.0

有用!您已經習慣於super()成功導航複雜的類層次結構,同時使用繼承和組合來創建具有最少重新實現的新類。

多重繼承替代方案

如您所見,多重繼承可能很有用,但也會導致非常複雜的情況和難以閱讀的代碼。擁有整齊地從多個其他對象繼承所有東西的對象也很少見。

如果您發現自己開始使用多重繼承和複雜的類層次結構,那麼值得問問自己是否可以通過使用組合而不是繼承來實現更清晰,更易於理解的代碼。

通過組合,您可以從一個稱為mixin的專用簡單類中為您的類添加非常特定的功能。

由於本文主要關注繼承,我不會詳細介紹組合以及如何在Python中使用它,但這裡有一個簡短的例子,VolumeMixin用於為我們的3D對象提供特定的功能 - 在這種情況下,是一個體積計算:

class Rectangle:

def __init__(self, length, width):

self.length = length

self.width = width

def area(self):

return self.length * self.width

class Square(Rectangle):

def __init__(self, length):

super().__init__(length, length)

class VolumeMixin:

def volume(self):

return self.area() * self.height

class Cube(VolumeMixin, Square):

def __init__(self, length):

super().__init__(length)

self.height = length

def area(self):

return super().area() * 6

在這個例子中,代碼被重新編寫為包含一個名為的mixin VolumeMixin。然後使用mixin Cube並提供Cube計算其體積的能力,如下所示:

>>>

>>> cube = Cube(2)
>>> cube.area()
24
>>> cube.volume()
48

這個mixin可以在任何具有為其定義的區域並且公式area * height返回正確音量的類中以相同的方式使用。


分享到:


相關文章: