从协议到抽象基类

序列协议

class Foo:
    def __getitem__(self, pos):
        return range(0, 30, 10)[pos]
    
f = Foo()
# 访问元素
f[1]
# Foo实例可迭代
for i in f: print(i)
# 也能支持in运算
20 in f
15 in f

如果没有__iter__和__contains__方法,Python会调用__getitem__方法,设法让迭代和in运算符可用。

动态协议和猴子补丁

  • FrenchDeck对象不支持赋值操作,因此内置的random.shuffle函数不能打乱实例
import collections

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

class FrenchDeck:
    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]
  • 为FrenchDeck打猴子补丁,把它变成可变的,让random.shuffle能够处理
    猴子补丁:在运行时修改类或模块,而不改动源码。
def set_card(deck, position, card):
    deck._cards[position] = card
    
FrenchDeck.__setitem__ = set_card
deck = FrenchDeck()
shuffle(deck)
deck[:5]

抽象基类

import abc

class Tombola(abc.ABC):
    
    @abc.abstractmethod
    def load(self, iterable):
        """"""
        
    @abc.abstractmethod
    def pick(self):
        """"""
    def loaded(self):
        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))
  • 抽象方法中必须有文档字符串,否则会报错
  • 实现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

@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))
issubclass(TomboList, Tombola) # 是Tombola子类
t = TomboList(range(100))
isinstance(t, Tombola) # TomboList实例是Tombola子类实例
# TomboList的超类
TomboList.__mro__
# 没有Tombola,因此没有从中继承任何方法

总结

  • 再次提到了序列协议的__getitem__方法
  • Python是动态语言,可以在程序运行是用猴子补丁修改类或模块
  • 定义抽象类和抽象方法
  • 现实抽象子类和虚拟子类

流畅的Python

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值