python鸭子类型与protocol

背景

  1. 大部分学python的朋友,可能都知道听说过鸭子类型。其实类型,就是那种会发出嘎嘎叫的类型,就叫类型(bushi,开玩笑的)
  2. 大部分学python的朋友,可能也都知道python也有所谓的静态类型检测。
  3. 那么 protocol和鸭子类型和类型检测到底是什么关系,这里我们来介绍一下。

详细介绍

类型检测

在写python的时候,大部分人可能是这么写的:

def cal_sum(x, y):
    res = x + y
    return res 

# use function 
value = cal_sum(1,2)

那么如果加上静态类型检测,应该是这么写的:

def cal_sum(x:int, y:int) -> int:
    res = x + y
    return res 

# use function 
value = cal_sum(1,2)

可以看到,在函数里面的两个参数x,y,已经函数输出后面,都加上int符号,表示函数接受的、输出的都是int类型的。

这也就是一个非常简单的案例,其实在真的大型的项目里面,静态类型可能要比这更加复杂。

虽然写起来复杂了,但是在大型项目里面,结合编辑器IDE的提醒,可以让我们写代码更加丝滑。

鸭子类型

在介绍之前,想问问大家,大家觉得什么什么,可以叫耳机?

  1. 可以发出声音的。
  2. 可以连接手机、电脑的。
  3. 可以戴在头上或者挂在耳朵上的。

好像只要满足上面几点,我们就可以判断这个东西是耳机了。

那么什么叫鸭子类型呢?很简单,就是那些发出嘎嘎嘎叫的小动物,就是鸭子。

说白了,就是我们从功能上定义,只要一个东西,符合某一功能的需求、或者说有对应的行为,那么就可以被判断为一个类型。

在百科上是这么说的:
“鸭子类型”的语言是这么推断的:一只鸟走起来像鸭子、游起泳来像鸭子、叫起来也像鸭子,那它就可以被当做鸭子。也就是说,它不关注对象的类型,而是关注对象具有的行为(方法)

  1. https://www.baike.com/wikiid/7643131612637675304?prd=mobile&view_id=qc73h8mqgxc00

protocol

protocol 也可以叫协议,在python3.8开始引入的。可以叫它为static duck typing(静态的鸭子类型)

用起来,也就是和抽象类abc差不多的。说起来挺乏力的,我们这里用一个例子来说。

案例

我们现在有一个案例:

1. 创建一个基础类

我们要创建一个飞人的类型。这个飞人类型将会在我们的未来世界中发挥关键的作用。

from typing import Protocol # line 1


class Flyer(Protocol): # line 2
    def fly(self) -> None: # line 3
        """
        A flyer can fly
        """
        ...   # line 4
  1. line 1表示:我们要从typing里面导出Protocol这个东西。
  2. line 2表示:我们要基于Protocol创建一个类,这个类叫Flyer
  3. line 3表示:定义了一个“行为”,叫fly
  4. line 4表示:...用来表示省略,也就是说:你到底怎么实现,不在乎,就用这个来替代就行了。

2. 创建超人

在我们未来的世界中,其实有很多超人,比如有会飞的超人,也有会跑的超人。


class FlyingHero: 
    """
    This hero can fly. 
    """
    def fly(self): # line 1
        ...

    def run(self) ->None:
        ...


class RunningHero:
    """
    This hero can run. 
    """
    def run(self): # line 2
        ...
  1. 注意在FlyingHero里面,也就是在line 1那里,我们给这个超人定义了fly功能。
  2. RunningHero里面,也就是在line 2哪里,这个超人,只能跑。没有fly哦。

3. 定义世界

在这里,我们定义一个世界。

class World:
    """An word that doesn't do anything."""

    def make_fly(self, obj:Flyer) -> None: # line 1
        return obj.fly()
  1. 在上面的line 1那一行,我们把obj设置为Flyer类型。
  2. 这其实就是告诉python、也告诉了现代编辑器IDE,这一行要求的对象,必须符合Flyer标准。

那么也都猜出来了,下面代码其实line 1运行其实是没问题的,但是line 2是有问题的.


def main()-> None:
    world= World()
    world.make_fly(FlyingHero()) # line 1
    world.make_fly(RunningHero())  # line 2


if __name__ =='__main__':
    main()

python鸭子类型与protocol_第1张图片

上面编辑器使用的是vscode,内置的插件Pylance提醒:RunningHero()的类型不符合obj要求的Flyer类型。

4. 发现

  1. 你仔细看代码,发现,FlyingHero()都没有基于Flyer类做继承,但是竟然通过了类型检查。
  2. 只是因为实现了相同的功能罢了:也就是都实现了Fly功能(或者叫行为)。

5. 进一步的

问一问自己,难道我使用继承不行么?

使用继承也可以解决这种类型检查的问题。

问的好,但是这个已经有答案了。

可以这么回答:

  1. 使用abc做一个抽象虚拟类,然后继承,当然也可以做到。但是使用protocol就不需要再做什么继承了。代码变简单了。
  2. 在使用第三方的包的时候,不需要再去考虑如何对第三方的一些类做继承,因为使用protocol知识只是检查行为是否相同,而不看别的内容。只要行为(或者叫methods)相同,那就通过类型检查。更简洁。

阅读更多

本来面向对象就已经够复杂了,还以为继承就是面向对象的一个非常牛的设计模式,结果现在有人对我说不用继承就可以实现类似的功能,我就很糊涂了。

那么如果你对继承真的非常熟悉,可以看看这个大哥的回答:

https://www.zhihu.com/question/547885140/answer/2625580341

这个大哥基于一个游戏设计的案例,介绍了在面向对象的时候,使用继承有多麻烦。

大致原因就是:本来使用继承就是为了方便复用,但是现实是不断的修改父类,导致子类在使用的过程中,会出现很多莫名其妙的情况。在这过程中,继承带来的优点就会被淹没。

其实我在学习的rust的时候,发现rust在做oop的时候,就是基于基础的数据结构和若干的方法组合在一起的。当时没感觉出来,但是后面看了越来越多的代码之后,恍然大悟。

你可能感兴趣的:(python,python,面向对象)