我们将遵循 Martelli 的建议,先利用现有的抽象基类(collections.MutableSequence),然后再斗胆自己定义。在示例 11-8 中,我们明确把 FrenchDeck2 声明为 collections.MutableSequence 的子类。

示例 11-8 frenchdeck2.py:FrenchDeck2collections.MutableSequence 的子类

import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])

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)

❶ 为了支持洗牌,只需实现 __setitem__ 方法。

❷ 但是继承 MutableSequence 的类必须实现 __delitem__ 方法,这是 MutableSequence 类的一个抽象方法。

❸ 此外,还要实现 insert 方法,这是 MutableSequence 类的第三个抽象方法。

导入时(加载并编译 frenchdeck2.py 模块时),Python 不会检查抽象方法的实现,在运行时实例化 FrenchDeck2 类时才会真正检查。因此,如果没有正确实现某个抽象方法,Python 会抛出 TypeError 异常,并把错误消息设为"Can't instantiate abstract class FrenchDeck2 with abstract methods __delitem__, insert"。正是这个原因,即便 FrenchDeck2 类不需要 __delitem__insert 提供的行为,也要实现,因为 MutableSequence 抽象基类需要它们。

如图 11-2 所示,SequenceMutableSequence 抽象基类的方法不全是抽象的。

{%}

图 11-2:MutableSequence 抽象基类和 collections.abc 中它的超类的 UML 类图(箭头由子类指向祖先;以斜体显示的名称是抽象类和抽象方法)

FrenchDeck2Sequence 继承了几个拿来即用的具体方法:__contains____iter____reversed__indexcountFrenchDeck2MutableSequence 继承了 appendextendpopremove__iadd__

collections.abc 中,每个抽象基类的具体方法都是作为类的公开接口实现的,因此不用知道实例的内部接口。

 要想实现子类,我们可以覆盖从抽象基类中继承的方法,以更高效的方式重新实现。例如,__contains__ 方法会全面扫描序列,可是,如果你定义的序列按顺序保存元素,那就可以重新定义 __contains__ 方法,使用 bisect 函数做二分查找(参见 2.8 节),从而提升搜索速度。

为了充分使用抽象基类,我们要知道有哪些抽象基类可用。接下来介绍集合抽象基类。