背景
- 大部分学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的提醒,可以让我们写代码更加丝滑。
鸭子类型
在介绍之前,想问问大家,大家觉得什么什么,可以叫耳机?
- 可以发出声音的。
- 可以连接手机、电脑的。
- 可以戴在头上或者挂在耳朵上的。
好像只要满足上面几点,我们就可以判断这个东西是耳机了。
那么什么叫鸭子类型呢?很简单,就是那些发出嘎嘎嘎叫的小动物,就是鸭子。
说白了,就是我们从功能上定义,只要一个东西,符合某一功能的需求、或者说有对应的行为,那么就可以被判断为一个类型。
在百科上是这么说的:
“鸭子类型”的语言是这么推断的:一只鸟走起来像鸭子、游起泳来像鸭子、叫起来也像鸭子,那它就可以被当做鸭子。也就是说,它不关注对象的类型,而是关注对象具有的行为(方法)
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
line 1表示:我们要从typing里面导出Protocol这个东西。line 2表示:我们要基于Protocol创建一个类,这个类叫Flyer。line 3表示:定义了一个“行为”,叫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
...
- 注意在
FlyingHero里面,也就是在line 1那里,我们给这个超人定义了fly功能。 - 在
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()
- 在上面的
line 1那一行,我们把obj设置为Flyer类型。 - 这其实就是告诉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()

上面编辑器使用的是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的时候,就是基于基础的数据结构和若干的方法组合在一起的。当时没感觉出来,但是后面看了越来越多的代码之后,恍然大悟。

23

被折叠的 条评论
为什么被折叠?



