【转载】 python鸭子类型与protocol
版权声明:本文为CSDN博主「yuanzhoulvpi」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yuanzhoulvpi/article/details/127525892
===============================================
背景
大部分学python的朋友,可能都知道听说过鸭子类型。其实🦆类型,就是那种会发出嘎嘎叫的类型,就叫🦆类型(bushi,开玩笑的)
大部分学python的朋友,可能也都知道python也有所谓的静态类型检测。
那么 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的提醒,可以让我们写代码更加丝滑。
鸭子类型
在介绍之前,想问问大家,大家觉得什么什么,可以叫耳机?
可以发出声音的。
可以连接手机、电脑的。
可以戴在头上或者挂在耳朵上的。
好像只要满足上面几点,我们就可以判断这个东西是耳机了。
那么什么叫鸭子类型呢?很简单,就是那些发出嘎嘎嘎叫的小动物,就是鸭子。
说白了,就是我们从功能上定义,只要一个东西,符合某一功能的需求、或者说有对应的行为,那么就可以被判断为一个类型。
在百科上是这么说的:
“鸭子类型”的语言是这么推断的:一只鸟走起来像鸭子、游起泳来像鸭子、叫起来也像鸭子,那它就可以被当做鸭子。也就是说,它不关注对象的类型,而是关注对象具有的行为(方法)
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

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()

def main()-> None: world= World() world.make_fly(FlyingHero()) # line 1 world.make_fly(RunningHero()) # line 2 if __name__ =='__main__': main()
那么也都猜出来了,下面代码其实line 1运行其实是没问题的,但是line 2是有问题的.

上面编辑器使用的是vscode,内置的插件Pylance提醒:RunningHero()的类型不符合obj要求的Flyer类型。
4. 发现
你仔细看代码,发现,FlyingHero()都没有基于Flyer类做继承,但是竟然通过了类型检查。
只是因为实现了相同的功能罢了:也就是都实现了Fly功能(或者叫行为)。
5. 进一步的
问一问自己,难道我使用继承不行么?
使用继承也可以解决这种类型检查的问题。
问的好,但是这个已经有答案了。
可以这么回答:
使用abc做一个抽象虚拟类,然后继承,当然也可以做到。但是使用protocol就不需要再做什么继承了。代码变简单了。
在使用第三方的包的时候,不需要再去考虑如何对第三方的一些类做继承,因为使用protocol知识只是检查行为是否相同,而不看别的内容。只要行为(或者叫methods)相同,那就通过类型检查。更简洁。
阅读更多
本来面向对象就已经够复杂了,还以为继承就是面向对象的一个非常牛的设计模式,结果现在有人对我说不用继承就可以实现类似的功能,我就很糊涂了。
那么如果你对继承真的非常熟悉,可以看看这个大哥的回答:
https://www.zhihu.com/question/547885140/answer/2625580341
这个大哥基于一个游戏设计的案例,介绍了在面向对象的时候,使用继承有多麻烦。
大致原因就是:本来使用继承就是为了方便复用,但是现实是不断的修改父类,导致子类在使用的过程中,会出现很多莫名其妙的情况。在这过程中,继承带来的优点就会被淹没。
其实我在学习的rust的时候,发现rust在做oop的时候,就是基于基础的数据结构和若干的方法组合在一起的。当时没感觉出来,但是后面看了越来越多的代码之后,恍然大悟。
===============================================
posted on 2023-01-19 11:34 Angry_Panda 阅读(88) 评论(1) 收藏 举报
浙公网安备 33010602011771号