Python文化中的接口和协议
Python
在ABCs
引入之前已经很成功了.接口在动态语言中是如何工作的。其没有interface
关键字。对于ABCs
,每个类有个接口。协议是接口。接口的定义是对象公共方法的子集,能够实现特定的功能。Python
中最基础的接口之一是序列协议
运行中实现协议
1 2 3
| l = list(range(10)) shuffle(l) print(l)
|
对于普通自定义对象,如果想使用shuffle
那么需要实现__setitem__
,因此可以动态设置xxx.__setitem__ = set_xxx
。但这其中暴露了__setitem__
给外界,破坏了封装性
ABC子类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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): self._cards[position] = value
def __delitem__(self, position): del self._cards[position] def insert(self, position, value): self._cards.insert(position, value)
|
继承链是MutableSequence
->Sequence
标准库中的ABCs
Python2.6
之后引入了ABCs
collections.abc
Iterable
, Container
, Sized
Sequence
, Mapping
, Set
MappingView
Callable
, Hashable
Iterator
除了collections.abc
之外,标准库中最有用的ABCs
就是numbers
numbers
Number
Complex
Real
Rational
Integral
因此我们需要使用isinstance(x, numbers.Integral)
来检查整形。需要注意的是decimal.Decimal
没有成为numbers.Real
的子类
定义和使用ABC
假设我们需要在网页或者APP
上随机展示广告。我们将定义名为Tombola
的抽象类。
Tombola
抽象类有四个方法。两个抽象方法是
load()
: 放条目到容器中
pick()
: 随机移除并返回条目
实体方法
loaded()
: 如果容器至少有一个条目,那么返回True
inspect()
: 返回排过序的的元组
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
| class Tombola(abc.ABC): @abc.abstractmethod def load(self, iterable): """Add items from an iterable."""
@abc.abstractmethod def pick(self): """Remove item at random, returning it.
This method should raise 'LookupError' when the instance is empty """
def loaded(self): """Return 'True' if there's at least 1 item, 'Falsle` otherwise.""" return bool(self.inspect())
def inspect(self): """Return a sorted tuple with the items currently inside.""" items = [] while True: try: items.append(self.pick()) except LookupError: break self.load(items) return tuple(sorted(items))
|
使用@abc.abstractmethod
标识抽象方法
1 2 3 4
| class Fake(Tombola): def pick(self): return 13
|
ABC详细语法
声明抽象类的最好方式是继承abc.ABC
或者其他ABC
。然而abc.ABC
是Python3.4
才引入的。在这此前必须使用metaclass=keyword
1 2
| class Tombola(metaclass=abc.ABCMeta):
|
metaclass=keyword
是Python3
才引入的,Python2
中,必须使用__metaclass__
1 2 3
| class Tombola(object): __metaclass__ = abc._ABCMeta
|
Tombola的子类
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 36 37 38 39 40
| class BingoCage(Tombola): def __init__(self, items): self._randomizer = random.SystemRandom() self._items = [] self.load(items)
def load(self, items): self._items.extend(items) self._randomizer.shuffle(self._items)
def pick(self): try: return self._items.pop() except IndexError: raise LookupError('pick from empty BingoCage')
def __call__(self): self.pick()
class LotteryBlower(Tombola): def __init__(self, iterable): self._balls = list(iterable)
def load(self, iterable): self._balls.extend(iterable)
def pick(self): try: position = random.randrange(len(self._balls)) except ValueError: raise LookupError('pick from empty BingoCage') return self._balls.pop(position)
def loaded(self): return bool(self._balls)
def inspect(self): return tuple(sorted(self._balls))
|
Tombola的虚子类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Tombola.register class TomboList(list): def pick(self): if self: position = randrange(len(self)) return self.pop(position) else: raise LookupError('pop from empty TomboList')
load = list.extend
def loaded(self): return bool(self)
def inspect(self): return tuple(sorted(self))
|
使用@Tombola.register
注册作为Tombola
的虚子类
注意因为注册了,所以issubclass
和isinstance
都能表现为TomboList
是Tombola
的子类。
但是打印继承关系
1 2 3 4
| print(TomboList.__mro__)
result: (<class '__main__.TomboList'>, <class 'list'>, <class 'object'>)
|
可以看出TomboList
并没有集成自Tombola
Tombola子类测试
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
| TEST_FILE = 'tombola_tests.rst' TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}'
def main(argv): verbose = '-v' in argv real_subclasses = Tombola.__subclasses__() virtual_subclasses = list(Tombola._abc_registry)
for cls in real_subclasses + virtual_subclasses: test(cls, verbose)
def test(cls, verbose=False): res = doctest.testfile( TEST_FILE, globs={'ConcreteTombola': cls}, verbose=verbose, optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
tag = 'FAIL' if res.failed else 'OK' print(TEST_MSG.format(cls.__name__, res, tag))
if __name__ == '__main__': import sys
main(sys.argv)
|