Python抽象基類的定義與使用
我們寫Python基本不需要自己創(chuàng)建抽象基類,而是通過鴨子類型來解決大部分問題?!读鲿车腜ython》作者使用了15年P(guān)ython,但只在項(xiàng)目中創(chuàng)建過一個(gè)抽象基類。我們更多時(shí)候是創(chuàng)建現(xiàn)有抽象基類的子類,或者使用現(xiàn)有的抽象基類注冊。本文的意義在于,了解抽象基類的定義與使用,可以幫助我們理解抽象基類是如何實(shí)現(xiàn)的,為我們以后學(xué)習(xí)后端語言(比如Java、Golang)打下基礎(chǔ)。畢竟抽象基類是編程語言通用設(shè)計(jì)。
定義抽象基類的子類
先回顧下什么是抽象基類:Python的抽象基類是指必須讓繼承它的子類去實(shí)現(xiàn)它所要求的抽象方法的類。如下代碼定義了抽象基類collections.MutableSequence的子類:
- 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)
通過抽象基類collections.MutableSequence源碼:
可以發(fā)現(xiàn),它有三個(gè)抽象方法__setitem__、__delitem__、insert,所以FrenchDeck2類必須實(shí)現(xiàn)它們。而對于其他非抽象方法比如append、extend、pop等,則可以直接繼承無需實(shí)現(xiàn)。
注意,Python只會在運(yùn)行時(shí)實(shí)例化FrenchDeck2類時(shí)真正檢查抽象方法的實(shí)現(xiàn),如果未實(shí)現(xiàn)會拋出TypeError異常,提示Can't instantiate abstract class之類的。
標(biāo)準(zhǔn)庫中的抽象基類
為了知道哪些抽象基類可以使用,我們可以看看標(biāo)準(zhǔn)庫。
collections.abc
collections.abc的抽象基類如下圖所示:
Iterable、Container、Sized
這三個(gè)抽象基類是最基礎(chǔ)的類,各個(gè)集合都繼承了這三個(gè)抽象基類。
- Itearble通過__iter__方法支持迭代
- Container通過__contains__方法支持in運(yùn)算符
- Sized通過__len__方法支持len()函數(shù)
Sequence、Mapping、Set
不可變集合類型,各自都有可變的子類。
MappingView
.items()、.keys()、.values()返回的對象分別是ItemsView、KeysView和ValuesView的實(shí)例。
Callable、Hashable
為內(nèi)置函數(shù)isinstance提供支持,判斷對象能不能調(diào)用或散列。
Iterator
迭代器。
numbers
numbers的抽象基類有以下幾種:
- Number
- Complex
- Real
- Rational
- Integral
這叫做數(shù)字塔,頂部是超類,底部是子類。比如使用isinstance(x, numbers.Integral)檢查一個(gè)數(shù)是不是整數(shù),這樣代碼就能接受int、bool(int的子類),再比如使用isinstance(x, numbers.Real)檢查浮點(diǎn)數(shù),這樣代碼就能接受bool、int、float、fractions.Fraction。
定義抽象基類
本小結(jié)可以跳過。不過了解抽象基類的定義有助于閱讀標(biāo)準(zhǔn)庫和其他包中的抽象基類源碼。
抽象基類的示例代碼如下:
- # 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)記抽象方法
- 抽象基類也可以包含普通方法
- 抽象基類的子類必須覆蓋抽象方法(普通方法可以不覆蓋),可以使用super()函數(shù)調(diào)用抽象方法,為它添加功能,而不是從頭開始實(shí)現(xiàn)
再看白鵝類型
白鵝類型的定義有一點(diǎn)難以理解,如果理解了虛擬子類,就能加快理解白鵝類型。虛擬子類并不是抽象基類的真正子類,而是注冊到抽象基類上的子類,這樣Python就不會做強(qiáng)制檢查了。
注冊的方式有兩種:
register方法
Python3.3以前只能使用register方法,比如collections.abc模塊的源碼中,把內(nèi)置類型tuple、str、range和memoryview注冊為Sequence的虛擬子類:
- Sequence.register(tuple)
- Sequence.register(str)
- Sequence.register(range)
- Sequence.register(memoryview)
register裝飾器
把TomboList注冊為Tombola的虛擬子類:
- @Tombola.register
- class TomboList(list):
- ...
白鵝類型和鴨子類型是Python的動(dòng)態(tài)特性,它們的共同點(diǎn)是,只要長的像,Python就不會做強(qiáng)制檢查,鴨子類型是針對普通類的子類而言的,白鵝類型是針對抽象基類的虛擬子類而言的。
參考資料:
《流暢的Python》第11章 接口:從協(xié)議到抽象基類