繼承和多態(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)
讓我們來看看一個繼承的例子 -
讓我們創(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)性是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__
位于父類中,因為子類-Dog
或Cat
沒有__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é)如下 -
函數(shù)(或方法)由def
語句創(chuàng)建。
雖然方法的工作方式與函數(shù)完全相同,除了方法第一個參數(shù)是實例對象的一點。
我們可以根據(jù)行為方式來分類方法
def outside_func(():
def func(self,)
@classmethod
def cfunc(cls,)
@staticmethod
def sfoo()
到目前為止,我們已經(jīng)看到了實例方法,下面來了解其他兩種方法。@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)方法既不接受自己也不接受cls(class)
參數(shù),但可以自由接受任意數(shù)量的其他參數(shù)。
語法
class C(object):
@staticmethod
def fun(arg1, arg2, ...):
...
returns: a static method for function funself.
什么時候使用