使用特殊方法的入门知识

一、先看例子

 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__除外)

posted on 2018-01-23 17:21  gl0899  阅读(90)  评论(0编辑  收藏  举报

导航