Python 中的鸭子类型(duck typing),协议和接口

得益于 Python 数据模型,自定义类型的行为可以像内置类型那样自然。实现如此自然的行为,靠的不是继承,而是鸭子类型:我们只需按照预定行为实现对象所需的方法即可。

“不要检查它是不是鸭子,它的叫声像不像鸭子,它走路姿势像不像鸭子等等,具体检查什么取决于你想使用语言的哪些特性”。鸭子类型:对象的类型无关紧要,只要实现了特定的协议即可。即忽略对象真正的类型,转而关注对象有没有实现所需的方法,签名和语义。

在面向对象编程中,协议是非正式的接口,只在文档中定义,在代码中不定义。例如,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._cards)

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

FrenchDeck 类能充分利用 Python 的很多功能(迭代,访问元素,in等等),因为它实现了序列协议,不过代码中并没有声明这一点。但是它明显是序列,即便它是 object 的子类也无所谓,我们说他是序列,因为它的行为像序列,这才是重点。

协议是非正式的,没有强制力,因此如果你知道类的具体使用场景,通常只需要实现一个协议的部分。例如,为了支持迭代,只需实现__getitem__方法,没必要提供__len__方法。协议是非正式的接口,让Python这种动态语言实现了多台的方式。

接口在动态语言中是怎么运作的呢?首先,基本的事实是,Python 语言没有 inferface 关键字,而且除了抽象基类,每个类都有接口:类实现或继承的公开属性(方法或数据属性),包括特殊方法,如__getitem__或__add__。按照定义,受保护的属性和私有属性不在接口中,即便‘受保护的’属性也只是采用命名约定时限的(单个前导下划线);私有属性可以轻松的访问。

关于接口:对象公开方法的子集,让对象在系统中扮演特定的角色。Python文档中的‘文件类对象’或‘可迭代对象’就是这个意思,这种说法指的不是特定的类。接口实现特定角色的方法的集合。

协议是接口,但不是正式的(只有文档和约定定义),因此协议不能像正式接口那样施加限制,一个类可能只实现部分接口,这是允许的。协议是动态的,函数不关心参数的类型,只要那个对象实现了部分可变序列协议即可。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值