一、先看例子
1 ### Frenchdeck ### 2 from collections import namedtuple 3 from random import choice 4 5 Card = namedtuple('Card', 'rank suit') 6 7 class Frenchdeck: 8 ranks = [str(i) for i in range(2, 11)] + list('JQKA') 9 suits = "spades hearts clubs diamonds".split() 10 11 def __init__(self): 12 self._cards = [Card(rank, suit) for suit in self.suits 13 for rank in self.ranks] 14 15 def __len__(self): 16 return len(self._cards) 17 18 def __getitem__(self, pos): 19 return self._cards[pos] 20 ### Frecchdeck ### 21 22 deck = Frenchdeck() 23 card1 = choice(deck) 24 card2, card3 = deck[0], deck[-1] 25 print(card1, card2, card3) 26 for card_A in deck[12::13]: 27 print(card_A) 28 print('Length of deck:', len(deck)) 29 for card in reversed(deck): 30 print(card)
二、分析原因
由上面的例子可以看出,一个只定义了__getitem__和__len__方法的类,其实例就能表现的几乎就像是一个序列类型对象(Sequence)。
原因如下:
1)在python中,定义了__len__方法以及以0开始的整数参数的__getitem__方法的类,即实现了对序列协议的支持;
2)支持序列协议的对象可以通过调用内置函数len()来触发解释器对__len__方法的调用,也可以通过[]操作符来对其属性中的数据项进行访问。
3)将对象属性deck._cards设置为一个以命名元组对象为元素的列表,通过这种与列表的“组合”,能委托列表对象,让其代理实现获取序列长度、索引和切片等操作。
4)序列协议是python最重要的基础协议,即便只有最简单的实现,python也会力求做到对序列协议的最大支持。
目前,在不知道序列协议的具体内容的前提下,通过学习_collections_abc.py的源码(已贴在下方)来体会这个协议可能支持的操作。
继承了Reversible 和 Collection的抽象基类abc.Sequence定义了四个特殊方法:__getitem__,__contains__, __iter__, __reversed__,及两个普通方法:index, count,其中只有__getitem__方法被定义为抽象方法(子类化时必须实现的方法)。 此抽象基类未实现对其父类Collection的__len__方法的覆盖,但在其docstring中,明确要求,抽象基类的子类必须覆盖__new__或__init__,和__len__, __getitem__。这可以说明序列协议对已这两个方法为基本要求事出有因。
其实只要定义了__getitem__方法,即使未定义__len__,都足够访问元素([]操作符),迭代和使用in运算符了。
在迭代时, python会启用__iter__方法的后备机制,即调用__getitem__方法,并传入从0开始的整数索引来尝试迭代对象。尽管没有实现__contains__
方法,python仍能通过尝试迭代来做全面成员检查。
5)想要对自定义类的对象调用内置函数reversed()时,要求自定义类必须实现了__reversed__方法或支持序列协议,即__len__和__getitem__同时出现才能实现reversed()的调用。
6)random.choice函数能接受任何实现了序列协议的对象为参数,进行随机取值。
7)通过组合的方式引入named tuple对象作为列表的元素,从而使deck对象中的元素的打印有个漂亮的显示(即使Frenchdeck类中定义了__repr__或__str__,也只能使deck对象本身的打印好看,而无法进一步影响到deck对象中的元素)
1 ### SEQUENCE ### 2 from collections.abc import * 3 from abc import abstractmethod 4 class Sequence(Reversible, Collection): 5 6 """All the operations on a read-only sequence. 7 8 Concrete subclasses must override __new__ or __init__, 9 __getitem__, and __len__. 10 """ 11 12 __slots__ = () 13 14 @abstractmethod 15 def __getitem__(self, index): 16 raise IndexError 17 18 def __iter__(self): 19 i = 0 20 try: 21 while True: 22 v = self[i] 23 yield v 24 i += 1 25 except IndexError: 26 return 27 28 def __contains__(self, value): 29 for v in self: 30 if v is value or v == value: 31 return True 32 return False 33 34 def __reversed__(self): 35 for i in reversed(range(len(self))): 36 yield self[i] 37 38 def index(self, value, start=0, stop=None): 39 '''S.index(value, [start, [stop]]) -> integer -- return first index of value. 40 Raises ValueError if the value is not present. 41 ''' 42 if start is not None and start < 0: 43 start = max(len(self) + start, 0) 44 if stop is not None and stop < 0: 45 stop += len(self) 46 47 i = start 48 while stop is None or i < stop: 49 try: 50 v = self[i] 51 if v is value or v == value: 52 return i 53 except IndexError: 54 break 55 i += 1 56 raise ValueError 57 58 def count(self, value): 59 'S.count(value) -> integer -- return number of occurrences of value' 60 return sum(1 for v in self if v is value or v == value) 61 62 Sequence.register(tuple) 63 Sequence.register(str) 64 Sequence.register(range) 65 Sequence.register(memoryview) 66 ### SEQUENCE ###
三、特殊方法使用基本要点
1)特殊方法是由解释器自动调用的,而非用户
2)用户可以通过一些内置函数(如len), 操作符(如[], ()), 及关键字(如in, with)等触发特殊方法的调用
3)使用内置函数来实现特殊方法的调用,相对于直接调用特殊方法而言更高效,且还能提供一些额外的服务。
4)对于用户,除非进行大量的元编程,不应频繁的调用特殊方法(在利用superclass帮组实现类的实例化时调用__init__除外)