Python抽象基類(lèi)的定義與使用
我們寫(xiě)Python基本不需要自己創(chuàng)建抽象基類(lèi),而是通過(guò)鴨子類(lèi)型來(lái)解決大部分問(wèn)題?!读鲿车腜ython》作者使用了15年P(guān)ython,但只在項(xiàng)目中創(chuàng)建過(guò)一個(gè)抽象基類(lèi)。我們更多時(shí)候是創(chuàng)建現(xiàn)有抽象基類(lèi)的子類(lèi),或者使用現(xiàn)有的抽象基類(lèi)注冊(cè)。本文的意義在于,了解抽象基類(lèi)的定義與使用,可以幫助我們理解抽象基類(lèi)是如何實(shí)現(xiàn)的,為我們以后學(xué)習(xí)后端語(yǔ)言(比如Java、Golang)打下基礎(chǔ)。畢竟抽象基類(lèi)是編程語(yǔ)言通用設(shè)計(jì)。
定義抽象基類(lèi)的子類(lèi)
先回顧下什么是抽象基類(lèi):Python的抽象基類(lèi)是指必須讓繼承它的子類(lèi)去實(shí)現(xiàn)它所要求的抽象方法的類(lèi)。如下代碼定義了抽象基類(lèi)collections.MutableSequence的子類(lèi):
- import collections
- Card = collections.namedtuple('Card', ['rank', 'suit'])
- class FrenchDeck2(collections.MutableSequence):
- ranks = [str(n) for n in range(2, 11)] + list('JQKA')
- suits = 'spades diamonds clubs hearts'.split()
- def __init__(self):
- self._cards = [Card(rank, suit) for suit in self.suits
- for rank in self.ranks]
- def __len__(self):
- return len(self._cards)
- def __getitem__(self, position):
- return self._cards[position]
- def __setitem__(self, position, value): # <1>
- self._cards[position] = value
- def __delitem__(self, position): # <2>
- del self._cards[position]
- def insert(self, position, value): # <3>
- self._cards.insert(position, value)
通過(guò)抽象基類(lèi)collections.MutableSequence源碼:
可以發(fā)現(xiàn),它有三個(gè)抽象方法__setitem__、__delitem__、insert,所以FrenchDeck2類(lèi)必須實(shí)現(xiàn)它們。而對(duì)于其他非抽象方法比如append、extend、pop等,則可以直接繼承無(wú)需實(shí)現(xiàn)。
注意,Python只會(huì)在運(yùn)行時(shí)實(shí)例化FrenchDeck2類(lèi)時(shí)真正檢查抽象方法的實(shí)現(xiàn),如果未實(shí)現(xiàn)會(huì)拋出TypeError異常,提示Can't instantiate abstract class之類(lèi)的。
標(biāo)準(zhǔn)庫(kù)中的抽象基類(lèi)
為了知道哪些抽象基類(lèi)可以使用,我們可以看看標(biāo)準(zhǔn)庫(kù)。
collections.abc
collections.abc的抽象基類(lèi)如下圖所示:
Iterable、Container、Sized
這三個(gè)抽象基類(lèi)是最基礎(chǔ)的類(lèi),各個(gè)集合都繼承了這三個(gè)抽象基類(lèi)。
- Itearble通過(guò)__iter__方法支持迭代
- Container通過(guò)__contains__方法支持in運(yùn)算符
- Sized通過(guò)__len__方法支持len()函數(shù)
Sequence、Mapping、Set
不可變集合類(lèi)型,各自都有可變的子類(lèi)。
MappingView
.items()、.keys()、.values()返回的對(duì)象分別是ItemsView、KeysView和ValuesView的實(shí)例。
Callable、Hashable
為內(nèi)置函數(shù)isinstance提供支持,判斷對(duì)象能不能調(diào)用或散列。
Iterator
迭代器。
numbers
numbers的抽象基類(lèi)有以下幾種:
- Number
- Complex
- Real
- Rational
- Integral
這叫做數(shù)字塔,頂部是超類(lèi),底部是子類(lèi)。比如使用isinstance(x, numbers.Integral)檢查一個(gè)數(shù)是不是整數(shù),這樣代碼就能接受int、bool(int的子類(lèi)),再比如使用isinstance(x, numbers.Real)檢查浮點(diǎn)數(shù),這樣代碼就能接受bool、int、float、fractions.Fraction。
定義抽象基類(lèi)
本小結(jié)可以跳過(guò)。不過(guò)了解抽象基類(lèi)的定義有助于閱讀標(biāo)準(zhǔn)庫(kù)和其他包中的抽象基類(lèi)源碼。
抽象基類(lèi)的示例代碼如下:
- # BEGIN TOMBOLA_ABC
- import abc
- class Tombola(abc.ABC): # <1>
- @abc.abstractmethod
- def load(self, iterable): # <2>
- """Add items from an iterable."""
- @abc.abstractmethod
- def pick(self): # <3>
- """Remove item at random, returning it.
- This method should raise `LookupError` when the instance is empty.
- """
- def loaded(self): # <4>
- """Return `True` if there's at least 1 item, `False` otherwise."""
- return bool(self.inspect()) # <5>
- def inspect(self):
- """Return a sorted tuple with the items currently inside."""
- items = []
- while True: # <6>
- try:
- items.append(self.pick())
- except LookupError:
- break
- self.load(items) # <7>
- return tuple(sorted(items))
- # END TOMBOLA_ABC
要點(diǎn):
- 繼承abc.ABC
- 使用@abc.abstractmethod裝飾器標(biāo)記抽象方法
- 抽象基類(lèi)也可以包含普通方法
- 抽象基類(lèi)的子類(lèi)必須覆蓋抽象方法(普通方法可以不覆蓋),可以使用super()函數(shù)調(diào)用抽象方法,為它添加功能,而不是從頭開(kāi)始實(shí)現(xiàn)
再看白鵝類(lèi)型
白鵝類(lèi)型的定義有一點(diǎn)難以理解,如果理解了虛擬子類(lèi),就能加快理解白鵝類(lèi)型。虛擬子類(lèi)并不是抽象基類(lèi)的真正子類(lèi),而是注冊(cè)到抽象基類(lèi)上的子類(lèi),這樣Python就不會(huì)做強(qiáng)制檢查了。
注冊(cè)的方式有兩種:
register方法
Python3.3以前只能使用register方法,比如collections.abc模塊的源碼中,把內(nèi)置類(lèi)型tuple、str、range和memoryview注冊(cè)為Sequence的虛擬子類(lèi):
- Sequence.register(tuple)
- Sequence.register(str)
- Sequence.register(range)
- Sequence.register(memoryview)
register裝飾器
把TomboList注冊(cè)為T(mén)ombola的虛擬子類(lèi):
- @Tombola.register
- class TomboList(list):
- ...
白鵝類(lèi)型和鴨子類(lèi)型是Python的動(dòng)態(tài)特性,它們的共同點(diǎn)是,只要長(zhǎng)的像,Python就不會(huì)做強(qiáng)制檢查,鴨子類(lèi)型是針對(duì)普通類(lèi)的子類(lèi)而言的,白鵝類(lèi)型是針對(duì)抽象基類(lèi)的虛擬子類(lèi)而言的。
參考資料:
《流暢的Python》第11章 接口:從協(xié)議到抽象基類(lèi)