在写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 也可以叫协议,在python3.8开始引入的。可以叫它为static duck typing(静态的鸭子类型)
用起来,也就是和抽象类abc差不多的。说起来挺乏力的,我们这里用一个例子来说。
我们现在有一个案例:
我们要创建一个飞人
的类型。这个飞人类型将会在我们的未来世界中发挥关键的作用。
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
表示:...
用来表示省略,也就是说:你到底怎么实现,不在乎,就用这个来替代就行了。在我们未来的世界中,其实有很多超人,比如有会飞的超人,也有会跑的超人。
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
哦。在这里,我们定义一个世界。
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
类型。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
类型。
FlyingHero()
都没有基于Flyer
类做继承,但是竟然通过了类型检查。Fly
功能(或者叫行为)。问一问自己,难道我使用继承不行么?
使用继承也可以解决这种类型检查的问题。
问的好,但是这个已经有答案了。
可以这么回答:
本来面向对象就已经够复杂了,还以为继承就是面向对象的一个非常牛的设计模式,结果现在有人对我说不用继承就可以实现类似的功能,我就很糊涂了。
那么如果你对继承真的非常熟悉,可以看看这个大哥的回答:
https://www.zhihu.com/question/547885140/answer/2625580341
这个大哥基于一个游戏设计的案例,介绍了在面向对象的时候,使用继承有多麻烦。
大致原因就是:本来使用继承就是为了方便复用,但是现实是不断的修改父类,导致子类在使用的过程中,会出现很多莫名其妙的情况。在这过程中,继承带来的优点就会被淹没。
其实我在学习的rust的时候,发现rust在做oop的时候,就是基于基础的数据结构和若干的方法组合在一起的。当时没感觉出来,但是后面看了越来越多的代码之后,恍然大悟。