Python文化中的接口和协议
Python在ABCs引入之前已经很成功了.接口在动态语言中是如何工作的。其没有interface关键字。对于ABCs,每个类有个接口。协议是接口。接口的定义是对象公共方法的子集,能够实现特定的功能。Python中最基础的接口之一是序列协议
运行中实现协议
| 12
 3
 
 | l = list(range(10))shuffle(l)
 print(l)
 
 | 
对于普通自定义对象,如果想使用shuffle那么需要实现__setitem__,因此可以动态设置xxx.__setitem__ = set_xxx。但这其中暴露了__setitem__给外界,破坏了封装性
ABC子类
| 12
 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(): 返回排过序的的元组
![tombola]()
| 12
 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标识抽象方法
| 12
 3
 4
 
 | class Fake(Tombola):def pick(self):
 return 13
 
 
 | 
ABC详细语法
声明抽象类的最好方式是继承abc.ABC或者其他ABC。然而abc.ABC是Python3.4才引入的。在这此前必须使用metaclass=keyword
| 12
 
 | class Tombola(metaclass=abc.ABCMeta):
 
 | 
metaclass=keyword是Python3才引入的,Python2中,必须使用__metaclass__
| 12
 3
 
 | class Tombola(object):__metaclass__ = abc._ABCMeta
 
 
 | 
Tombola的子类
| 12
 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的虚子类
| 12
 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的子类。
但是打印继承关系
| 12
 3
 4
 
 | print(TomboList.__mro__)
 result:
 (<class '__main__.TomboList'>, <class 'list'>, <class 'object'>)
 
 | 
可以看出TomboList并没有集成自Tombola
Tombola子类测试
| 12
 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)
 
 
 |