文章目錄
- 一、繼承
- 1. 常見數列案例
- 2. 常見數列實作
- 數列基類
- 等引數列
- 等比數列
- 斐波那契數列
- 二、抽象基類
- 三、介面
先一句話總結Python中繼承、抽象基類和介面三者之間的關系:Python中的介面機制可通過抽象基類實作,介面的實作有賴于繼承機制,
一、繼承
繼承是面向物件編程語言的三大特性之一(其他兩個是封裝、多型),所謂繼承是指子類自動具有父類所定義的方法和屬性,而無需子類再重復定義同名的方法或屬性,因此繼承的最大優勢之一是可以提高代碼的復用程度,
1. 常見數列案例
這里以高中數學中一個重要的概念——數列來簡介Python的繼承概念,數列是一組數值組成的序列,該序列中的每一個值都取決于數列的前一項或多項,例如:
- 對于等引數列,數列中從第二項開始,每一項都由前一項加上一個固定的常量得到;
- 對于等比數列,數列中從第二項開始,每一項都由前一項乘上一個固定的常量得到;
- 對于斐波那契數列,數列中從第三項開始,每一項都有其前兩項之和相加得到,
如果現在需要使用面向物件特性對上述各個不同型別的數列進行代碼抽象,則可以想到三個數列類必然都支持下列類似功能的方法:
- 初始化方法:用于初始化數列的前若干項;
- 遍歷支持方法:可以支持以可迭代的方式遍歷出數列的項;
- 數列項生成方法:按照一定的規則根據前若干項生成任意項,
如果不采用繼承的方式,則最終實作的各數列類必然代碼重復度很高,
2. 常見數列實作
針對上述討論,下面考慮使用繼承實作各個數列類:
- 首先,定義一個通用數列父類
Progression,在其中實作數列的共有方法及實用方法; - 然后,繼承
Progression類再根據數列通項生成規則分別在等差、等比、斐波那契數列中重寫父類方法或定義全新方法,
數列基類
在數列基類中:
__init__初始化方法接收兩個引數,start用以指定數列第一項的值,num用以指定默認列印的數列項數,分別用于初始化_current和_num的值;_advance方法用于按照數列通項規則生成任意項;__iter__和__next__方法用以支持Python的迭代器協議(具體請見Python中for回圈運行機制探究以及可迭代物件、迭代器詳解),用于數列的遍歷;__str__方法用于將數列物件轉換為串列,并回傳該串列的字串表示形式,
class Progression:
"""數列基類"""
def __init__(self, start=0, num=10):
"""
將當前數列的第一項初始化為0
:param start: 數列第一項,默認為0
:param num: 列印數列時的默認顯示項數
"""
self._current = start
self._num = num
def _advance(self):
"""用于根據數列前若干項進行任意項的生成,該方法應該被子類重寫"""
self._current += 1
def __next__(self):
"""迭代器協議方法,回傳數列中的下一項,當已至數列最后一項則拋出StopIteration例外"""
if self._num > 0:
ans = self._current
self._advance()
self._num -= 1
return ans
else:
raise StopIteration
def __iter__(self):
"""迭代器協議方法,回傳物件自身"""
return self
def __str__(self):
"""回傳物件的字串表示形式"""
return str(list(self))
等引數列
在等引數列的實作中,由于繼承了Progression類,所以:
__init__方法中呼叫了父類初始化方法,從而對繼承自父類的_current以及默認列印的數列項數_num進行了初始化,另外還對該類特有的等引數列常量_increment進行了初始化;_advance方法按照等引數列通項規則對父類同名方法進行了重寫;__next__、__iter__和__str__方法繼承自Progression類,無需重復撰寫代碼,
class ArithmeticProgression(Progression):
"""等引數列"""
def __init__(self, start=0, increment=1, num=10):
"""
創建一個新的等引數列
:param increment: 等差常量,默認為1
:param start: 數列首項,默認為0
:param num: 列印數列時的默認顯示項數
"""
super().__init__(start=start, num=num)
self._increment = increment
def _advance(self): # 重寫父類同名方法
"""根據等引數列通項規則,生成任意項"""
self._current += self._increment
等比數列
在等比數列的實作中,由于繼承了Progression類,所以:
__init__方法中呼叫了父類初始化方法,從而對繼承自父類的_current以及默認列印的數列項數_num進行了初始化,另外還對該類特有的等比數列常量_base進行了初始化;_advance方法按照等比數列通項規則對父類同名方法進行了重寫;__next__、__iter__和__str__方法繼承自Progression類,無需重復撰寫代碼,
class GeometricProgression(Progression):
"""等比數列"""
def __init__(self, start=1, num=10, base=2):
"""
創建一個新的等比數列
:param base: 等比常量,默認值為2
:param start: 數列首項,默認為1
:param num: 列印數列時的默認顯示項數
"""
super().__init__(start=start, num=num)
self._base = base
def _advance(self):
"""根據等比數列通項規則,生成任意項"""
self._current *= self._base
斐波那契數列
在斐波那契數列的實作中,由于繼承了Progression類,所以:
__init__方法中呼叫了父類初始化方法,從而對繼承自父類的_current以及默認列印的數列項數_num進行了初始化,另外還對該類特有的假想第0項_prev進行了初始化;_advance方法按照斐波那契數列通項規則對父類同名方法進行了重寫;__next__、__iter__和__str__方法繼承自Progression類,無需重復撰寫代碼,
class FibonacciProgression(Progression):
"""斐波那契數列"""
def __init__(self, first=0, second=1, num=10):
"""
創建一個新的斐波那契數列
:param first: 數列第一項,默認為0
:param second: 數列第二項,默認為1
:param num: 列印數列時的默認顯示項數
"""
super().__init__(start=first, num=num)
self._prev = second - first # 假想在第一項之前存在的第零項
def _advance(self):
"""根據斐波那契數列通項規則,生成任意項"""
self._prev, self._current = self._current, self._prev + self._current
下面是對上述幾個數列實作類的測驗結果:
if __name__ == '__main__':
print('默認數列Progression:')
print(Progression(num=5), end='\n'*2) # [0, 1, 2, 3, 4]
print('等引數列ArithmeticProgression:')
print(ArithmeticProgression(start=10, increment=3, num=7), end='\n'*2) # [10, 13, 16, 19, 22, 25, 28]
print('等比數列GeometricProgression:')
print(GeometricProgression(start=4, base=3, num=9), end='\n'*2) # [4, 12, 36, 108, 324, 972, 2916, 8748, 26244]
print('斐波那契數列FibonacciProgression:')
print(FibonacciProgression(first=2, num=12)) # [2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123, 199]
二、抽象基類
仔細分析上述代碼可知,基類Progression僅是為了作為ArithmeticProgression、GeometricProgression以及FibonacciProgression的基類,雖然可以通過實體化Progression得到一個物件,但這意義不大,因為其僅是ArithmeticProgression的一種特殊情況,即第一項為0,等差常量為1的等引數列,
在支持面向物件編程范式的語言中,對Progression這種僅作為基類用于指定多個子類所需實作的方法的類,有一個專門的術語——抽象基類,
在Python3中想要定義一個抽象基類,可通過如下步驟實作:
- 在定義抽象基類前,從模塊
abc中匯入類ABCMeta和方法abstractmethod; - 在定義抽象基類時:
- 在抽象基類名后指定
metaclass為ABCMeta; - 在抽象基類需被子類繼承后實作的方法(一般稱為抽象方法)前使用
@abstractmethod,
- 在抽象基類名后指定
例如:如前所述,對于Progression方法,按照上述流程將其定義為抽象基類的代碼如下:
from abc import ABCMeta, abstractmethod
class Progression(metaclass=ABCMeta):
"""數列基類"""
def __init__(self, start=0, num=10):
"""
將當前數列的第一項初始化為0
:param start: 數列第一項,默認為0
:param num: 列印數列時的默認顯示項數
"""
self._current = start
self._num = num
@abstractmethod
def _advance(self):
"""用于根據數列前若干項進行任意項的生成,該方法應該被子類重寫"""
def __next__(self):
"""迭代器協議方法,回傳數列中的下一項,當已至數列最后一項則拋出StopIteration例外"""
if self._num > 0:
ans = self._current
self._advance()
self._num -= 1
return ans
else:
raise StopIteration
def __iter__(self):
"""迭代器協議方法,回傳物件自身"""
return self
def __str__(self):
"""回傳物件的字串表示形式"""
return str(list(self))
可以看出,上述代碼中我們將_advance定義成了抽象方法,因為該方法一方面在所有子類中都必須存在,另一方面該方法在所有子類中的實作又都完全不同,
需要指出的是,對于抽象基類(如:Progression)不能直接對其通過實體化創建物件,否則會報這樣的錯誤:TypeError: Can't instantiate abstract class Progression with abstract methods _advance,
三、介面
介面是一種編程機制,這種機制可以確保不同的代碼撰寫者可以:
- 遵循相同的代碼簽名,如:方法名稱(
_advance)、引數、回傳值; - 使用不同的演算法實作具體代碼,如:根據等差、等比、斐波那契數列的通項生成規則實作
_advance方法,
介面機制的好處在于,可以:
- 實作不同代碼撰寫者之間的協作,如:應用架構者可以對整體框架做搭建,而將具體實作留給實施人員,這有點像你的老板一般會告訴你要做根據手頭的資源某幾件事情以及期望結果是什么,而你和你的同事需要通過一定的程序努力將每一件事具體實施好;
- 實作代碼間的松耦合,各個介面的實作人員無需了解其他人員對介面的內部具體實作,
Python中,對于介面的具體實作,只要在子類中繼承抽象基類,然后實作其中的所有抽象方法即可,
下面還是以上述的數列類為例演示介面實作的程序:
from abc import ABCMeta, abstractmethod
class Progression(metaclass=ABCMeta):
"""數列基類"""
def __init__(self, start=0, num=10):
"""
將當前數列的第一項初始化為0
:param start: 數列第一項,默認為0
:param num: 列印數列時的默認顯示項數
"""
self._current = start
self._num = num
@abstractmethod
def _advance(self):
"""用于根據數列前若干項進行任意項的生成,該方法應該被子類重寫"""
def __next__(self):
"""迭代器協議方法,回傳數列中的下一項,當已至數列最后一項則拋出StopIteration例外"""
if self._num > 0:
ans = self._current
self._advance()
self._num -= 1
return ans
else:
raise StopIteration
def __iter__(self):
"""迭代器協議方法,回傳物件自身"""
return self
def __str__(self):
"""回傳物件的字串表示形式"""
return str(list(self))
class FibonacciProgression(Progression):
"""斐波那契數列"""
def __init__(self, first=0, num=10, second=1):
"""
創建一個新的斐波那契數列
:param first: 數列第一項,默認為0
:param second: 數列第二項,默認為1
:param num: 列印數列時的默認顯示項數
"""
super().__init__(start=first, num=num)
self._prev = second - first # 假想在第一項之前存在的第零項
def _advance(self):
"""根據斐波那契數列通項規則,生成任意項"""
self._prev, self._current = self._current, self._prev + self._current
if __name__ == '__main__':
print(FibonacciProgression(first=2, num=12)) # [2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123, 199]
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/41852.html
標籤:其他
