Python - 协议和鸭子类型

参考:

  1. Fluent_Python - P430
  2. wiki

这里说的协议是什么?是让Python这种动态类型语言实现多态的方式。

  1. 在面向对象编程中,协议是非正式的接口,是一组方法,但只是一种文档,语言不对施加特定的措施或者强制实现。
  2. 虽然协议是非正式的,在Python中,应该把协议当成正式的接口。
  3. Python中存在多种协议,用于实现鸭子类型(对象的类型无关紧要,只要实现了特定的协议(一组方法)即可)
  4. 需要成为相对应的鸭子类型,那就实现相关的协议,即相关的__method__。例如实现序列协议(len__和__getitem),这个类就表现得像序列。
  5. 协议是正式的,没有强制力,可以根据具体场景实现一个具体协议的一部分。例如,为了支持迭代,只需实现__getitem__,不需要实现__len__。
  6. 在Python文档中,如果看到“文件类对象“(表现得像文件的对象),通常说的就是协议,这个对象就是鸭子类型。这是一种简短的说法,意思是:“行为基本与文件一致,实现了部分文件接口,满足上下文相关需求的东西。”

鸭子类型(Duck Typing)

  1. When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck. - James Whitcomb Riley
  2. 不关注对象的类型,而关注对象的行为(方法)。它的行为是鸭子的行为,那么可以认为它是鸭子。

例子1. 让FrenchDeck类表现的像Python的序列一样,FrenchDeck就是鸭子类型。

  1. FrenchDeck类是哪个类的子类,是什么类型,都没有关系。只要提供所需的方法即可,例如表现得像序列一样。
  2. Python的序列协议只需要__len__和__getitem__两个方法。
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._cars)

    def __getitem__(self, position):
        return self._cards[position]

这里,FrenchDeck实现了Python序列协议所需的__len__和__getitem__方法,它就是鸭子类型,表现得和序列一样。

posted @ 2018-04-17 10:38  Rocin  阅读(2348)  评论(0编辑  收藏  举报