1. 程式人生 > 其它 >從協議到抽象基類

從協議到抽象基類

技術標籤:Pythonpython

序列協議

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