Python中介面與抽象類別之設計:abc模組(Abstract Base Classes)
使用 Python abc 模組的原因是為了要解決 Python 沒有「抽象類別 (abstract class)」的問題。透過抽象類別,我們可以建立一個比使用 hasattr() 還要更嚴格的類別介面 (class interface) 檢查。舉例而言,當我們建立一個動物的類別,我們希望之後繼承實作的類別都一定要有「screaming」以及「walk」的方法,我們可以透過 abc.ABCMeta 這個 metaclass 來定義 Animal 抽象類別:
import abc #注意:此非採用 fromabc import ABCMeta,abstractmethod 方式
class Animal(metaclass=abc.ABCMeta): @abc.abstractmethod def screaming(self): 'Return when animal screaming the sound hear likes' raise NotImplemented
@abc.abstractmethod def walk(self, x, y): 'Make animal walk to position (x, y).' raise NotImplemented |
如果不透過 metaclass指定方式,我們也可以透過繼承 abc.ABC 這個 helper class 達到相同的效果:
class Animal(abc.ABC): @abc.abstractmethod def screaming(self): 'Return when animal screaming the sound hear likes' raise NotImplemented
@abc.abstractmethod def walk(self, x, y): 'Make animal walk to position (x, y).' raise NotImplemented |
當我們繼承使用 Animal 這個抽象類別來建立類別時,就必須要實作 screaming 以及 walk ;如果沒有實作的話,Python 就會產生 TypeError:
... pass ... >>> Dog() # Create a instance(注:Python實例化物件不使用new關鍵字) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class Dog with abstract methods screaming, walk |
... x = 0 ... y = 0 ... def screaming(self): ... return 'Wof, Wof' ... def walk(self, x, y): ... self.x = x ... self.y = y ... return (self.x, self.y) ... >>> Dog() # Create a instance <__main__.Dog object at 0x7fc207b10208> >>> dog = Dog() >>> dog.screaming() 'Wof, wof!' >>> dog.walk(10, 20) (10, 20) |
我們可透過 isinstance 以及 issubclass 方法來檢查 Dog 的類別:
True >>> issubclass(Dog, Animal) True |
如果有一天,我們想要把一個Animal可視為Python built-in 的 list 类型來使用(*即[Animal]),我們可以透過 register 來改變這個事實:
False >>> issubclass(list, Animal) False >>> Animal.register(list) # Recognize Animal as list (*即Animal具有list功能) >>> isinstance([], Animal) True >>> issubclass(list, Animal) True |
這個用法在 collections.abc 被大量的使用,CPython 在 collections.abc 中定義了許多的基礎抽象類別,例如說 Sequence、Iterable 等。現實中Sequence 可符合定義的類別有:tuple、str、range、memoryview。我們可以在原始碼看到他們被 Sequence 所 register:
Sequence.register(tuple) Sequence.register(str) Sequence.register(range) Sequence.register(memoryview) |
接著,我們就可以使用 Sequence 這個抽象類別來作 list 以及 tuple 功能,在這邊我們稱作為 virtual 化:
>>> from collections import abc >>> isinstance([], abc.Sequence) True >>> isinstnace((), abc.Sequence) True >>> issubclass(list, abc.Sequence) True >>> issubclass(tuple, abc.Sequence) True |
前面提到了抽象方法,那其它的類別方法或靜態方法也能夠被抽象化嗎?答案是可以,自 Python 3.3 版後,只需要在 method decorator後再加上 @abstractmethod 即可。
class Base(abc.ABC): @classmethod @abc.abstractmethod def setUpClass(cls): raise NotImplemented
@staticmethod @abc.abstractmethod def count(self, data): raise NotImplemented
class Implementation(Base): @classmethod def setUpClass(cls): cls.count = 0
@staticmethod def count(self, data): self.count = len(data) return self.count |
在 class 裡面的 property 也可以作抽象化,同樣在 decorator後再加上 @abstractmethod 即可:
class Base(abc.ABC): _index = 0
@property @abc.abstractmethod def index(self): raise NotImplemented
@index.setter @abc.abstractmethod def index(self, new_index): raise NotImplemented
class Implementation(Base): MAX_LEN = 100
@property def index(self): return self._index
@index.setter def index(self, new_index): new_index = min(new_index, self.MAX_LEN) self._index = new_index
imp = Implementation() print(imp.index) imp.index = 50 print(imp.index) imp.index = 500 print(imp.index) |
ABCMeta 是 metaclass,因此在__new__ 方法內會作 abstract 檢查的部分。
class ABC(metaclass=ABCMeta): """Helper class that provides a standard way to create an ABC using inheritance. """ __slots__ = () |
透過 metaclass=ABCMeta 來指定metaclass此可讓一般類別繼承 ABC時即具有 ABCMeta 的功能。接者把 __slots__ 設為 (),這樣的設定讓其他人無法修改此 ABC 的 attributes。
>>> import abc >>> class Foo(object): ... pass ... >>> foo = Foo() >>> foo.bar = 10 #可 >>> print(foo.bar) 10 >>> a = abc.ABC() >>> a.bar = 10 #不可 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'ABC' object has no attribute 'bar' |
有關 __slots__ 的介紹,請參考 __slots__ 以及 使用 __slots__。