abc — Abstract Base Classes
This module provides the infrastructure for defining abstract base classes (ABCs) in Python, as outlined in PEP 3119; see the PEP for why this was added to Python. (See also PEP 3141 and the
numbers
module regarding a type hierarchy for numbers based on ABCs.)
官方介紹文件:29.7. abc — Abstract Base Classes
01. Quickstart Tutorial
使用 Python abc 套件的原因,是為了要解決 Python 沒有「抽象類別 (abstract class)」的問題。透過抽象類別,我們可以建立一個比起使用 hasattr()
還要更嚴格的類別介面 (class interface) 檢查。
舉例而言,我們來建立一個動物的類別,我們希望之後繼承實作的類別都一定要有「screaming」以及「walk」的方法,我們可以透過 abc.ABCMeta
這個 metaclass 來實作 Animal 抽象類別:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import abc class Animal(metaclass=abc.ABCMeta): @abc.abstractmethod def screaming(self): ‘Return when animal screaming the sound hear likes’ return NotImplemented @abc.abstractmethod def walk(self, x, y): ‘Make animal walk to position (x, y).’ return NotImplemented |
如果不透過 metaclass,我們也可以透過繼承 abc.ABC
這 helper class 達到相同的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import abc class Animal(abc.ABC): @abc.abstractmethod def screaming(self): ‘Return when animal screaming the sound hear likes’ return NotImplemented @abc.abstractmethod def walk(self, x, y): ‘Make animal walk to position (x, y).’ return NotImplemented |
當我們使用 Animal 這個抽象類別來建立類別時,就必須要實作 screaming
以及 walk
。如果沒有實作的話,Python 就會產生 TypeError:
1 2 3 4 5 6 7 |
>>> class Dog(Animal): ... pass ... >>> Dog() # Create a instance Traceback (most recent call last): File “<stdin>”, line 1, in <module> TypeError: Can‘t instantiate abstract class Dog with abstract methods screaming, walk |
我們現在來實作 Dog 這個類別:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
>>> class Dog(Animal): ... 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() <__main__.Dog object at 0x7fc207b10208> >>> dog = Dog() >>> dog.screaming() ‘Wof, wof!’ >>> dog.walk(10, 20) (10, 20) |
我們也可以透過 isinstance 以及 issubclass 檢查 Dog 的類別:
1 2 3 4 |
>>> isinstnace(Dog(), Animal) True >>> issubclass(Dog, Animal) True |
如果有一天,我們認為 Python built-in 的 list
也是一個 Animal,我們可以透過 register
來改變這個事實:
1 2 3 4 5 6 7 8 9 |
>>> isinstance([], Animal) False >>> issubclass(list, Animal) False >>> Animal.register(list) # Recognize list as Animal >>> isinstance([], Animal) True >>> issubclass(list, Animal) True |
這個用法在 collections.abc
被大量的使用,CPython 在 collections.abc 中定義了許多的基礎抽象類別,例如說 Sequence
、Iterable
等。現實中符合 Sequence 定義的類別有這些:tuple
、str
、range
、memoryview
。我們可以在原始碼看到,他們被 Sequence register 過:
1 2 3 4 |
Sequence.register(tuple) Sequence.register(str) Sequence.register(range) Sequence.register(memoryview) |
接著,我們就可以使用 Sequence 這個抽象類別來比較 list 以及 tuple (即使 list 以及 tuple 沒有繼承自 Sequence)。在這邊我們稱作為 virtual subclasses:
1 2 3 4 5 6 7 8 9 |
>>> from collections import abc >>> isinstance([], abc.Sequence) True >>> isinstnace((), abc.Sequence) True >>> issubclass(list, abc.Sequence) True >>> issubclass(tuple, abc.Sequence) True |
02. HOW-TO GUIDE
前面提到了抽象方法,那其他的類別屬性也能夠抽象化嗎?答案是可以,自 Python 3.3 版後,只需要在 method decorator 後面加上 abstractmethod 即可:
抽象化 classmethod 以及 staticmethod
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import abc class Base(abc.ABC): @classmethod @abc.abstractmethod def setUpClass(cls): return NotImplemented @staticmethod @abc.abstractmethod def count(self, data): return len(data) class Implementation(Base): @classmethod def setUpClass(cls): cls.count = 0 @staticmethod def count(self, data): self.count = len(data) return self.count |
抽象化 property
在 class 裡面的 property 也可以要求抽象化,同樣在 decorator 下面加上 abstractmethod 即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import abc class Base(abc.ABC): _index = 0 @property @abc.abstractmethod def index(self): return self._index @index.setter @abc.abstractmethod def index(self, new_index): self._index = new_index 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) |
03. DISCUSSIONS
> abc 的運作原理?
abc.ABCMeta 主要做到三件事情:
- ABCMeta 是 metaclass,因此 override __new__ 方法,在這裡面會初始化 abstract 檢查的部分。
- override __isinstancecheck__
- override __issubclasscheck__
> 如何做到 decorator + abstractmethod 而不需要相對應的 abstract decorator (@abstractclassmethod…etc)?
請參考 commit: bfebb7b54a50f011 以及 bpo-11610。
簡而言之,透過新增加 C-API _PyObject_IsAbstract()
,在 descrobject 以及 funcobject 的 tp_getset
slot 裡面檢查是否有 __isabstractmethod__
的參數即可。在 object.c 加入了 _PyObject_IsAbstract()、descrobject.c 加入 property_getsetlist、funcobject.c 分別加入 cm_getsetlist 以及 sm_getsetlist 到 tp_getset slot。
> abc.ABC helper class 的運作原理?
abc.ABC 程式碼如下:
1 2 3 4 5 |
class ABC(metaclass=ABCMeta): “””Helper class that provides a standard way to create an ABC using inheritance. “”” __slots__ = () |
大略說明,透過 metaclass=ABCMeta
來繼承 metaclass 的 method,讓普通 class 繼承 ABC 也會有 ABCMeta 的功能。接者把 __slots__ 設為 ()
,這樣的設定讓其他人無法修改 ABC instance 的 attributes。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> 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__。
> get_cache_token 的實際用途?
請參考 functools.py,除了這邊我找不到其他實際用途……
> abc.ABCMeta Performance Issue
請參考下列 mailing list:
- https://mail.python.org/pipermail/python-ideas/2017-July/046395.html
- https://www.mail-archive.com/[email protected]/msg96362.html
abc.ABCMeta 對於 Python startup time 有著嚴重的影響。測出的結果,創立一個 abc.ABCMeta 的 class 會比普通的 class 慢上兩倍以上。同時 abc.ABCMeta 用了三個 weakset,這也是可以改善的地方。
目前上面 mailing list 提出的方法有幾種,一種是把 abc.py 使用 C 改寫、另一種是改善 weakset 的效能,或是改用其他方式。
04. REFERENCE
- 官方介紹文件– 29.7. abc — Abstract Base Classes
- PEP 3119 – Introduce Abstract Base Classes
- PEP 3141 – A Type Hierarchy for Numbers
- collections.abc– Abstract Base Classes for Containers
- abc – Abstract Base Classes – MOTW3
- What is metaclass? – Stackoverflow best answer of metaclass
Leave a Reply