繼承和多態

繼承和多態 - 這是Python中一個非常重要的概念。我們必須更好地理解它。

繼承

面向對象編程的一個主要優勢是重用。 繼承是實現這一目標的機制之一。 繼承允許程序員先創建一個通用類或基類,然後再將其擴展爲更專門化的類。 它允許程序員編寫更好的代碼。

使用繼承,可以使用或繼承基類中可用的所有數據字段和方法。 之後,可以添加自己的方法和數據字段,因此繼承提供了一種組織代碼的方法,而不是從頭開始重寫。

在面向對象的術語中,當類X擴展類Y時,則Y稱爲超級/父/基類,X稱爲子類/子/派生類。 這裏需要注意的一點是,只有數據字段和非專用的方法才能被子類訪問。 私有數據字段和方法只能在類中訪問。

創建派生類的語法是 -

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

繼承屬性

現在看下面的代碼例子 -


class Date(object):
    def get_date(self):
        return "2018-06-30"

class Time(Date):
    def get_time(self):
        return "09:09:09"

dt = Date()
print("Get date from Date class: ", dt.get_date())

tm = Time()
print("Get time from Time class: ", tm.get_time())
print("Get date from class by inheriting or calling Date class method: ", tm.get_date())

執行上面示例代碼,得到以下結果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
Get date from Date class:  2018-06-30
Get time from Time class:  09:09:09
Get date from class by inheriting or calling Date class method:  2018-06-30

首先創建了一個名爲Date的類,並將該對象作爲參數傳遞,object是由Python提供的內置類。 之後創建了另一個名爲time的類,並將Date類稱爲參數。 通過這個調用,可以訪問Date類中的所有數據和屬性。 正因爲如此,創建的Time類對象tm中獲取父類中get_date方法。

Object.Attribute查找層次結構

  • 實例
  • 當前類
  • 該類繼承的任何父類

繼承示例

讓我們來看看一個繼承的例子 -

繼承和多態

讓我們創建幾個類來參與示例 -

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

在Python中,類的構造函數用於創建對象(實例),併爲屬性賦值。

子類的構造函數總是調用父類的構造函數來初始化父類中的屬性的值,然後它開始爲其屬性賦值。


class Animal(object):
    def __init__(self, name):
        self.name = name

    def eat(self, food):
        print('%s is eating %s , '%(self.name, food))

class Dog(Animal):

    def fetch(self, thing):
        print('%s goes after the %s !'%(self.name, thing))


class Cat(Animal):

    def swatstring(self):
        print('%s shreds the string! ' % (self.name))


d = Dog('Ranger')
c = Cat("Meow")

d.fetch("ball");
c.swatstring()
d.eat("Dog food")
c.eat("Cat food")
## 調用一個沒有的定義的方法
#d.swatstring()

執行上面示例,得到以下代碼 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
Ranger goes after the ball !
Meow shreds the string! 
Ranger is eating Dog food , 
Meow is eating Cat food ,

在上面的例子中,我們看到如何父類中的屬性或方法,以便所有的子類或子類都會從父類繼承那些屬性。

如果一個子類嘗試從另一個子類繼承方法或數據,那麼它將通過一個錯誤,就像看到當Dog類嘗試調用cat類有swatstring()方法時,它會拋出一個錯誤(AttributeError)。

多態性(「多種形狀」)

多態性是Python中類定義的一個重要特性,當您在類或子類中使用通用命名方法時,可以使用它。 這允許功能在不同時間使用不同類型的實體。 所以,它提供了靈活性和鬆散耦合,以便代碼可以隨着時間的推移而擴展和輕鬆維護。

這允許函數使用任何這些多態類的對象,而不需要知道跨類的區別。

多態性可以通過繼承進行,子類使用基類方法或覆蓋它們。

我們用之前的繼承示例理解多態的概念,並在兩個子類中添加一個名爲show_affection的常用方法 -

從這個例子可以看到,它指的是一種設計,其中不同類型的對象可以以相同的方式處理,或者更具體地說,兩個或更多的類使用相同的名稱或通用接口,因爲同樣的方法(下面的示例中的show_affection) 用任何一種類型的對象調用。


class Animal(object):
    def __init__(self, name):
        self.name = name

    def eat(self, food):
        print('%s is eating %s , '%(self.name, food))

class Dog(Animal):

    def fetch(self, thing):
        print("{0} wags {1}".format(self.name, thing))

    def show_affection(self):
        print("{0} wags tail ".format(self.name))

class Cat(Animal):

    def swatstring(self):
        print('%s shreds the string! ' % (self.name))

    def show_affection(self):
        print("{0} purrs ".format(self.name))

d = Dog('Ranger')
c = Cat("Meow")

d.show_affection()
c.show_affection()

執行上面示例代碼,得到以下結果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
Ranger wags tail 
Meow purrs

所以,所有的動物都表現出喜愛(show_affection),都不太相同。 「show_affection」行爲因此具有多態性,因爲它根據動物的不同而採取不同的行爲。 因此,抽象的「動物」概念實際上並不是「show_affection」,而是特定的動物(如狗和貓)具有動作「show_affection」的具體實現。

Python本身具有多態的類。 例如,len()函數可以與多個對象一起使用,並且都會根據輸入參數返回正確的輸出。

繼承和多態

重載

在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__位於父類中,因爲子類-DogCat沒有__init__方法。 Python使用繼承屬性查找來查找動物類中的__init__。 當我們創建子類時,首先它會查找dog類中的__init__方法,如果它找不到它,則查找父類Animal,並在那裏找到並在那裏調用它。 因此,當類設計變得複雜時,可能希望初始化一個實例,首先通過父類構造函數然後通過子類構造函數處理它。參考以下示例代碼 -


import random

class Animal(object):

    def __init__(self, name):
        self.name = name

class Dog(Animal):

    def __init__(self, name):
        super(Dog, self).__init__(name)
        self.breed = random.choice(['Doberman', 'German shepherd', 'Beagle'])

    def fetch(self, thing):
        print('%s goes after the %s !'%(self.name, thing))

d = Dog('黑皮')
print(d.name)
print(d.breed)

執行上面示例代碼,得到以下結果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
黑皮
German shepherd

在上面的例子中,所有的動物都有一個名字,所有的狗都是一個特定的品種。我們用super來調用父類構造函數。所以Dog類有它自己的__init__方法,但首先調用的我們稱之爲超類。 Super是在函數中構建的,它是用來將一個類與它的超類或它的父類關聯起來。

在這種情況下,我們說獲取超類-Dog並將它實例傳遞給構造函數 - __init__。換句話說,我們用dog對象調用父類Animal.__init__方法。你可能會問,爲什麼不會只用Animal.__init__()Dog的實例,我們可以做到這一點,但如果Animal類的名字會在將來某個時候改變。如果想重新安排類層次結構,那麼該Dog會從另一個類繼承。在這種情況下使用super可以讓保持模塊化,易於更改和維護。

所以在這個例子中,能夠將通用__init__功能與更具體的功能相結合。這使有機會將通用功能與特定功能分開,從而消除代碼重複,並以反映系統總體設計的方式將類相互關聯。

結論

  • __init__與任何其他方法一樣; 它可以被繼承
  • 如果一個類沒有__init__構造函數,Python將檢查其父類查找。
  • 只要找到有一個__init__構造函數,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

看下面的多重繼承圖 -

繼承和多態

通過一個例子來理解Python的「mro」特性。


class A(object):

    def dothis(self):
        print('doing this in A')

class B(A):
    pass

class C(object):
    def dothis(self):
        print('doing this in C')

class D(B, C):
    pass

d_inst =  D()
d_inst.dothis()
print(D.mro())

執行上面示例代碼,得到以下結果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
doing this in A
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>, <class 'object'>]

示例3

下面再來看另一個「菱形」多重繼承的例子。

繼承和多態

上圖將被視爲含糊不清。 從上之前的例子中瞭解「方法解析順序」。 mro將是D→B→A→C→A,但事實並非如此。 在從C獲得第二個A時,Python將忽略之前的A。因此在這種情況下mro將是D→B→C→A

我們來根據上面的圖創建一個例子 -


class A(object):

    def dothis(self):
        print('doing this in A')

class B(A):
    pass

class C(A):
    def dothis(self):
        print('doing this in C')

class D(B, C):
    pass

d_inst =  D()
d_inst.dothis()
print(D.mro())

執行上面示例代碼,得到以下結果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
doing this in C
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

理解上述輸出的簡單規則是 - 如果在方法分辨率順序中出現相同的類,則將從方法分辨率順序中刪除此類的早期外觀。

總結如下 -

  • 任何類都可以從多個類繼承
  • 搜索繼承類時,Python通常使用「深度優先」順序。
  • 但是,當兩個類從同一個類繼承時,Python將從該mro中消除該類的第一次出現。

裝飾器,靜態和類方法

函數(或方法)由def語句創建。

雖然方法的工作方式與函數完全相同,除了方法第一個參數是實例對象的一點。

我們可以根據行爲方式來分類方法

  • 簡單的方法 - 在類的外部定義。 該函數可以通過提供實例參數來訪問類屬性:

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

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

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

    @staticmethod
    def sfoo()

    到目前爲止,我們已經看到了實例方法,下面來了解其他兩種方法。

1. 類方法

[@classmethod](https://github.com/classmethod "@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參數,它不能修改對象實例狀態。

  • 它受到類的約束,而不是類的對象。
  • 類方法仍然可以修改適用於類的所有實例的類狀態。

2. 靜態方法

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

語法

class C(object):
   @staticmethod
   def fun(arg1, arg2, ...):
   ...
returns: a static method for function funself.
  • 靜態方法既不能修改對象狀態,也不能修改類的狀態。
  • 受限於可以訪問的數據。

什麼時候使用

  • 通常使用類方法來創建工廠方法。 工廠方法返回不同用例的類對象(類似於構造函數)。
  • 通常使用靜態方法來創建實用函數。