鍍金池/ 教程/ Python/ 繼承和多態(tài)
文件和字符串
異常和異常類
Python面向?qū)ο蠛喗?/span>
面向?qū)ο蠼輳?/span>
對象序列化
數(shù)據(jù)結(jié)構(gòu)
開發(fā)環(huán)境設(shè)置
設(shè)計模式
類庫
構(gòu)建塊
繼承和多態(tài)
高級特性
Python面向?qū)ο蠼坛?/span>

繼承和多態(tài)

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

繼承

面向?qū)ο缶幊痰囊粋€主要優(yōu)勢是重用。 繼承是實現(xiàn)這一目標(biāo)的機制之一。 繼承允許程序員先創(chuàng)建一個通用類或基類,然后再將其擴展為更專門化的類。 它允許程序員編寫更好的代碼。

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

在面向?qū)ο蟮男g(shù)語中,當(dāng)類X擴展類Y時,則Y稱為超級/父/基類,X稱為子類/子/派生類。 這里需要注意的一點是,只有數(shù)據(jù)字段和非專用的方法才能被子類訪問。 私有數(shù)據(jù)字段和方法只能在類中訪問。

創(chuàng)建派生類的語法是 -

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

繼承屬性

現(xiàn)在看下面的代碼例子 -


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())

執(zhí)行上面示例代碼,得到以下結(jié)果 -

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

首先創(chuàng)建了一個名為Date的類,并將該對象作為參數(shù)傳遞,object是由Python提供的內(nèi)置類。 之后創(chuàng)建了另一個名為time的類,并將Date類稱為參數(shù)。 通過這個調(diào)用,可以訪問Date類中的所有數(shù)據(jù)和屬性。 正因為如此,創(chuàng)建的Time類對象tm中獲取父類中get_date方法。

Object.Attribute查找層次結(jié)構(gòu)

  • 實例
  • 當(dāng)前類
  • 該類繼承的任何父類

繼承示例

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

讓我們創(chuàng)建幾個類來參與示例 -

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

在Python中,類的構(gòu)造函數(shù)用于創(chuàng)建對象(實例),并為屬性賦值。

子類的構(gòu)造函數(shù)總是調(diào)用父類的構(gòu)造函數(shù)來初始化父類中的屬性的值,然后它開始為其屬性賦值。


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")
## 調(diào)用一個沒有的定義的方法
#d.swatstring()

執(zhí)行上面示例,得到以下代碼 -

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 ,

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

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

多態(tài)性(“多種形狀”)

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

這允許函數(shù)使用任何這些多態(tài)類的對象,而不需要知道跨類的區(qū)別。

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

我們用之前的繼承示例理解多態(tài)的概念,并在兩個子類中添加一個名為show_affection的常用方法 -

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


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()

執(zhí)行上面示例代碼,得到以下結(jié)果 -

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

所以,所有的動物都表現(xiàn)出喜愛(show_affection),都不太相同。 “show_affection”行為因此具有多態(tài)性,因為它根據(jù)動物的不同而采取不同的行為。 因此,抽象的“動物”概念實際上并不是“show_affection”,而是特定的動物(如狗和貓)具有動作“show_affection”的具體實現(xiàn)。

Python本身具有多態(tài)的類。 例如,len()函數(shù)可以與多個對象一起使用,并且都會根據(jù)輸入?yún)?shù)返回正確的輸出。

重載

在Python中,當(dāng)子類包含一個覆蓋超類方法的方法時,也可以通過調(diào)用超類方法 -

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!')

繼承構(gòu)造函數(shù)

從前面的繼承示例中看到,__init__位于父類中,因為子類-DogCat沒有__init__方法。 Python使用繼承屬性查找來查找動物類中的__init__。 當(dāng)我們創(chuàng)建子類時,首先它會查找dog類中的__init__方法,如果它找不到它,則查找父類Animal,并在那里找到并在那里調(diào)用它。 因此,當(dāng)類設(shè)計變得復(fù)雜時,可能希望初始化一個實例,首先通過父類構(gòu)造函數(shù)然后通過子類構(gòu)造函數(shù)處理它。參考以下示例代碼 -


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)

執(zhí)行上面示例代碼,得到以下結(jié)果 -

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

在上面的例子中,所有的動物都有一個名字,所有的狗都是一個特定的品種。我們用super來調(diào)用父類構(gòu)造函數(shù)。所以Dog類有它自己的__init__方法,但首先調(diào)用的我們稱之為超類。 Super是在函數(shù)中構(gòu)建的,它是用來將一個類與它的超類或它的父類關(guān)聯(lián)起來。

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

所以在這個例子中,能夠?qū)⑼ㄓ?code>__init__功能與更具體的功能相結(jié)合。這使有機會將通用功能與特定功能分開,從而消除代碼重復(fù),并以反映系統(tǒng)總體設(shè)計的方式將類相互關(guān)聯(lián)。

結(jié)論

  • __init__與任何其他方法一樣; 它可以被繼承
  • 如果一個類沒有__init__構(gòu)造函數(shù),Python將檢查其父類查找。
  • 只要找到有一個__init__構(gòu)造函數(shù),Python就會調(diào)用它并停止查找
  • 可以使用super()函數(shù)來調(diào)用父類中的方法。
  • 可能想要在父類以及子類進行初始化。

多重繼承和查找樹

正如其名稱所示,Python的多重繼承是當(dāng)一個類從多個類繼承時。

例如,一個孩子繼承父母(母親和父親)的個性特征。

Python多繼承語法

要使一個類繼承多個父類,可將這些類的名稱寫在派生類的括號內(nèi),同時定義它。 我們用逗號分隔這些名字。

下面是一個例子 -

>>> class Mother:
   pass

>>> class Father:
   pass

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

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

多繼承是指從兩個或兩個以上的類繼承的能力。 當(dāng)孩子從父母繼承而父母從祖父母類繼承時,復(fù)雜性就出現(xiàn)了。 Python在繼承樹上尋找正在被請求從對象讀取的屬性。 它將檢查實例,在類中然后在父類中檢查,最后從祖父類中檢查。 現(xiàn)在問題出現(xiàn)在按什么順序搜索類 - 廣度優(yōu)先或深度優(yōu)先。 默認(rèn)情況下,Python采用深度優(yōu)先。

這就是為什么在下圖中Python首先在A類中搜索dothis()方法。所以下面例子中的方法解析順序?qū)⑹?-

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())

執(zhí)行上面示例代碼,得到以下結(jié)果 -

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。

我們來根據(jù)上面的圖創(chuàng)建一個例子 -


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())

執(zhí)行上面示例代碼,得到以下結(jié)果 -

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'>]

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

總結(jié)如下 -

  • 任何類都可以從多個類繼承
  • 搜索繼承類時,Python通常使用“深度優(yōu)先”順序。
  • 但是,當(dāng)兩個類從同一個類繼承時,Python將從該mro中消除該類的第一次出現(xiàn)。

裝飾器,靜態(tài)和類方法

函數(shù)(或方法)由def語句創(chuàng)建。

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

我們可以根據(jù)行為方式來分類方法

  • 簡單的方法 - 在類的外部定義。 該函數(shù)可以通過提供實例參數(shù)來訪問類屬性:
    def outside_func(():
    
  • 實例方法 -
    def func(self,)
    
  • 類方法 - 如果需要使用類屬性
    @classmethod
    def cfunc(cls,)
    
  • 靜態(tài)方法 - 沒有關(guān)于該類的任何信息
    @staticmethod
    def sfoo()
    
    到目前為止,我們已經(jīng)看到了實例方法,下面來了解其他兩種方法。

1. 類方法

@classmethod裝飾器是一個內(nèi)置的函數(shù)裝飾器,它通過調(diào)用它的類或作為第一個參數(shù)調(diào)用的實例的類。 評估結(jié)果會影響函數(shù)定義。

語法

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

他們有權(quán)訪問此cls參數(shù),它不能修改對象實例狀態(tài)。

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

2. 靜態(tài)方法

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

語法

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

什么時候使用

  • 通常使用類方法來創(chuàng)建工廠方法。 工廠方法返回不同用例的類對象(類似于構(gòu)造函數(shù))。
  • 通常使用靜態(tài)方法來創(chuàng)建實用函數(shù)。

上一篇:異常和異常類下一篇:類庫