繼承和多型



繼承和多型——這是Python中一個非常重要的概念。如果你想學習,必須更好地理解它。

繼承

面向物件程式設計的主要優勢之一是程式碼重用。繼承是實現此目標的機制之一。繼承允許程式設計師首先建立一個通用或基礎類,然後將其擴充套件到更專業的類。它允許程式設計師編寫更好的程式碼。

使用繼承,您可以使用或繼承基礎類中可用的所有資料欄位和方法。稍後您可以新增自己的方法和資料欄位,因此繼承提供了一種組織程式碼的方式,而不是從頭開始重寫。

在面向物件的術語中,當類X擴充套件類Y時,Y稱為超類/父類/基類,X稱為子類/子類/派生類。這裡需要注意的一點是,只有非私有的資料欄位和方法才能被子類訪問。私有資料欄位和方法只能在類內部訪問。

建立派生類的語法為:

class BaseClass:
   Body of base class
class DerivedClass(BaseClass):
   Body of derived class

繼承屬性

現在看看下面的例子:

Inheriting Attributes

輸出

Inheriting Attributes Output

我們首先建立了一個名為Date的類,並將物件作為引數傳遞,這裡物件是Python提供的內建類。稍後我們建立了另一個名為time的類,並將Date類作為引數呼叫。透過此呼叫,我們可以訪問Time類中Date類中的所有資料和屬性。正因為如此,當我們嘗試從之前建立的Time類物件tm獲取get_date方法時,這是可能的。

物件.屬性查詢層次結構

  • 例項
  • 任何此類繼承的類

繼承示例

讓我們仔細看看繼承示例:

Inheritance Example

讓我們建立幾個類來參與示例:

  • Animal - 模擬動物的類
  • Cat - Animal的子類
  • Dog - Animal的子類

在Python中,類的建構函式用於建立物件(例項),併為屬性賦值。

子類的建構函式始終呼叫父類的建構函式來初始化父類中屬性的值,然後開始為其自己的屬性賦值。

Python Constructor

輸出

Python Constructor Output

在上面的示例中,我們看到了在父類中放置的命令屬性或方法,以便所有子類或子類將繼承父類的該屬性。

如果子類試圖從另一個子類繼承方法或資料,則會丟擲錯誤,就像我們在Dog類嘗試從Cat類呼叫swatstring()方法時看到的那樣,它會丟擲錯誤(在我們的例子中是AttributeError)。

多型(“多種形態”)

多型是Python中類定義的一個重要特性,當您在類或子類中具有同名方法時會使用它。這允許函式在不同時間使用不同型別的實體。因此,它提供了靈活性和松耦合,以便程式碼可以隨著時間的推移進行擴充套件和輕鬆維護。

這允許函式使用任何這些多型類的物件,而無需瞭解類之間的區別。

多型可以透過繼承來實現,子類利用基類方法或覆蓋它們。

讓我們用之前的繼承示例來理解多型的概念,並在兩個子類中新增一個名為show_affection的通用方法:

從示例中我們可以看到,它指的是一種設計,其中不同型別的物件可以以相同的方式對待,或者更具體地說,兩個或多個類具有相同名稱的方法或通用介面,因為相同的方法(下面的示例中的show_affection)被用於任何型別的物件。

Polymorphism

輸出

Polymorphism Output

因此,所有動物都表現出感情(show_affection),但它們的方式不同。因此,“show_affection”行為在某種意義上是多型的,因為它根據動物的不同而表現不同。因此,抽象的“動物”概念實際上並不“表現出感情”,但特定的動物(如狗和貓)對“表現出感情”的行為有具體的實現。

Python本身包含多型的類。例如,len()函式可以用於多個物件,並且所有函式都根據輸入引數返回正確的輸出。

Polymorphic

覆蓋

在Python中,當子類包含覆蓋超類方法的方法時,您還可以透過呼叫以下方法來呼叫超類方法

Super(Subclass, self).method 而不是 self.method。

示例

class Thought(object):
   def __init__(self):
      pass
   def message(self):
      print("Thought, always come and go")

class Advice(Thought):
   def __init__(self):
      super(Advice, self).__init__()
   def message(self):
      print('Warning: Risk is always involved when you are dealing with market!')

繼承建構函式

如果我們從之前的繼承示例中看到,__init__位於父類中,因為子類dog或cat在其內部沒有__init__方法。Python使用繼承屬性查詢在animal類中查詢__init__。當我們建立子類時,首先它將在dog類中查詢__init__方法,然後它沒有找到它,然後在父類Animal中查詢並找到它並在那裡呼叫它。因此,隨著我們類設計的複雜化,我們可能希望首先透過父類建構函式處理例項,然後透過子類建構函式處理例項。

Constructor

輸出

Constructor Output

在上面的示例中,所有動物都有一個名字,所有狗都有一個特定的品種。我們使用super呼叫了父類建構函式。因此,dog有自己的__init__,但首先發生的事情是我們呼叫super。Super是內建函式,它旨在將類與其超類或父類關聯。

在這種情況下,我們說獲取dog的超類並將dog例項傳遞給我們在這裡說的任何方法,即建構函式__init__。換句話說,我們正在使用dog物件呼叫父類Animal __init__。您可能會問為什麼我們不只是用dog例項說Animal __init__,我們可以這樣做,但如果animal類的名稱將來發生變化。如果我們想重新排列類層次結構,那麼dog從另一個類繼承。在這種情況下使用super允許我們保持事物模組化並易於更改和維護。

因此,在這個例子中,我們能夠將通用__init__功能與更具體的**功能結合起來。這給了我們機會將通用功能與特定功能分開,這可以消除程式碼重複,並以反映系統整體設計的方式將類相互關聯。

結論

  • __init__就像任何其他方法一樣;它可以被繼承

  • 如果一個類沒有__init__建構函式,Python將檢查其父類以檢視是否可以找到一個。

  • 一旦找到一個,Python就會呼叫它並停止查詢

  • 我們可以使用super()函式呼叫父類中的方法。

  • 我們可能希望在父類和我們自己的類中進行初始化。

多重繼承和查詢樹

顧名思義,Python中的多重繼承是指一個類繼承自多個類。

例如,一個孩子繼承父母雙方的性格特徵(母親和父親)。

Python多重繼承語法

要使一個類繼承自多個父類,我們在定義派生類時將這些類的名稱寫在派生類的括號內。我們用逗號分隔這些名稱。

下面是一個例子:

>>> class Mother:
   pass

>>> class Father:
   pass

>>> class Child(Mother, Father):
   pass

>>> issubclass(Child, Mother) and issubclass(Child, Father)
True

多重繼承指的是從兩個或多個類繼承的能力。複雜性在於子類從父類繼承,而父類又從祖父母類繼承。Python會沿著繼承樹向上查詢被請求從物件讀取的屬性。它將在例項、類中檢查,然後是父類,最後是祖父母類。現在問題出現了,類將以什麼順序被搜尋——廣度優先還是深度優先。預設情況下,Python使用深度優先。

這就是為什麼在下面的圖表中,Python首先在類A中搜索dothis()方法。因此,在下面的示例中,方法解析順序將為

Mro- D→B→A→C

檢視下面的多重繼承圖:

Multiple Inheritance

讓我們透過一個例子來理解Python的“mro”特性。

輸出

Python mro Feature Output

示例3

讓我們再舉一個“菱形”多重繼承的例子。

Diamond Shape Multiple Inheritance

上面的圖表將被認為是模稜兩可的。從我們之前理解“方法解析順序”的例子中。即mro將是D→B→A→C→A,但事實並非如此。在從C獲取第二個A時,Python會忽略之前的A。因此,在這種情況下,mro將為D→B→C→A。

讓我們根據上面的圖表建立一個例子:

Method Resolution Order

輸出

Method Resolution Order Output

理解上面輸出的簡單規則是——如果同一個類出現在方法解析順序中,則該類的早期出現將從方法解析順序中刪除。

總之:

  • 任何類都可以繼承自多個類

  • Python通常在搜尋繼承類時使用“深度優先”順序。

  • 但是當兩個類繼承自同一個類時,Python會從mro中刪除該類的第一次出現。

裝飾器、靜態方法和類方法

函式(或方法)由def語句建立。

儘管方法的工作方式與函式完全相同,但有一點不同,即方法的第一個引數是例項物件。

我們可以根據方法的行為對其進行分類,例如

  • 簡單方法 - 在類外部定義。此函式可以透過提供例項引數來訪問類屬性

def outside_func(():
  • 例項方法 -

def func(self,)
  • 類方法 - 如果我們需要使用類屬性

   @classmethod
def cfunc(cls,)
  • 靜態方法 - 沒有關於類的任何資訊

      @staticmethod
def sfoo()

到目前為止,我們已經看到了例項方法,現在是時候深入瞭解其他兩種方法了,

類方法

@classmethod 裝飾器是一個內建的函式裝飾器,它將呼叫它的類或呼叫它的例項的類作為第一個引數傳遞。該評估的結果會覆蓋你的函式定義。

語法

class C(object):
   @classmethod
   def fun(cls, arg1, arg2, ...):
      ....
fun: function that needs to be converted into a class method
returns: a class method for function

它們可以訪問這個 cls 引數,但不能修改物件例項的狀態。這需要訪問 self。

  • 它繫結到類,而不是類的物件。

  • 類方法仍然可以修改適用於類所有例項的類狀態。

靜態方法

靜態方法既不接受 self 引數也不接受 cls(類)引數,但可以自由接受任意數量的其他引數。

語法

class C(object):
   @staticmethod
   def fun(arg1, arg2, ...):
   ...
returns: a static method for function funself.
  • 靜態方法既不能修改物件狀態也不能修改類狀態。
  • 它們在可以訪問的資料方面受到限制。

何時使用什麼

  • 我們通常使用類方法來建立工廠方法。工廠方法為不同的用例返回類物件(類似於建構函式)。

  • 我們通常使用靜態方法來建立實用程式函式。

廣告