Python 协议和抽象基类

python 序列协议

Sequence
Reversible
,
Collection
__getitem__
,
__len__
__contains__
__iter__
__reversed__
,
index
, and 
count

>>> class Foo:
	def __getitem__(self, pos):
		return range(0, 10)[pos]

	
>>> f = Foo()
>>> f[1]
1
>>> list(f)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> 2 in f
True
>>> 20 in f
False

Foo 类没有继承 abc.Sequence, 而且只实现了序列协议的一个方法: __getitem__ (没有实现 __len__ 方 法),足够访问元素、 迭代(__iter__)和使用 in(__contains__) 运算符了。

MutableSequence
Sequence
__getitem__
,
__setitem__
,
__delitem__
,
__len__
insert
Inherited 
Sequence
 methods and 
append
reverse
extend
pop
remove
, and 
__iadd__

>>> class Foo:
	def __init__(self):
		self.data = [1, 2, 3, 4, 5]
	def __getitem__(self, pos):
		return self.data[pos]
	def __setitem__(self, pos, value):
		self.data[pos] = value
	def __len__(self):
		return len(self.data)

	
>>> f = Foo()
>>> random.shuffle(f)
>>> list(f)
[2, 4, 1, 3, 5]

Foo 类没有继承 abc.MutableSequence,通过实现可变序列协议(__len__、__setitem__)。

实现特定的协议(对象类型无关紧要)也称为“鸭子类型”。

Sized
 
__len__
 

>>> class Struggle:
	def __len__(self):
		return 10

	
>>> from collections import abc
>>> isinstance(Struggle(), abc.Sized)
True

无需显式继承、注册,abc.Sized 也能把 Struggle 识别为自己的子类, 只要实现了特殊方法 __len__ 即可。

继承

import collections

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

class FrenchDeck(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)

继承必须实现抽象方法。

抽象基类

标准库中的抽象基类

自定义抽象基类

import abc

class Tombola(abc.ABC):
    """抽象基类要继承 abc.ABC"""
    
    @abc.abstractmethod
    def load(self, iterable):
        """从可迭代对象中添加元素"""


    @abc.abstractmethod
    def pick(self):
        """随机删除元素,然后将其返回。
            如果实例为空,抛出'LookupError'"""


    def loaded(self):
        """如果至少有一个元素, 返回`True`, 否则返回`False`。 """
        return bool(self.inspect())


    def inspect(self):
        """返回一个有序元组, 由当前元素构成。 """
        items = []
        while True:
            try:
                items.append(self.pick())
            except LookupError:
                break
        self.load(items)
        return tuple(sorted(items))
            

import random
from tombola import Tombola


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:
            # IndexError 是 LookupError 的子类
            raise LookupError('pick from empty BingoCage')


    def __call__(self):
        self.pick()



import random
from tombola import Tombola


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 LotteryBlower')
        return self._balls.pop(position)


    def loaded(self):
        return bool(self._balls)


    def inspect(self):
        return tuple(sorted(self._balls))

虚拟子类

from random import randrange
from tombola import Tombola


@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(TomboList) # 方法注册虚拟子类

>>> from tombola import Tombola
>>> from tombolist import TomboList
>>> issubclass(TomboList, Tombola)
True
>>> t = TomboList(range(100))
>>> isinstance(t, Tombola)
True

注意:

>>> TomboList.__mro__
(<class 'tombolist.TomboList'>, <class 'list'>, <class 'object'>)
  

TomboList 类的 __mro__ 属性, 只有“真实的”超类,没有 Tombola, 因此 Tombolist 不会从 Tombola 中继承任何方法

register的方式:

Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
Sequence.register(memoryview)

展开阅读全文