学习FastAPI的入门文档,突然发现对标注很陌生,简单了解记录一下。
捋一捋
- Python 3.0 引入函数注解(Function Annotations),PEP 3107
- Python 3.5 引入类型提示(Type Hints),用于函数注解,PEP 484
- Python 3.6 在3.5基础上,引入了变量注解,PEP 526
- Python 3.8 引入静态鸭子类型(Protocols),PEP 544
- Python 3.8 引入TypedDict,PEP 589
- Python 3.9 引入灵活的函数与变量注解,PEP 539
PEP 3107 引入函数注解(Function Annotations)
- https://peps.python.org/pep-3107/
背景:Python2.x 中,没有一个标准的方式 对函数的参数和返回值进行标注。故而在Python3.0中,引入一个单一和标准的方式来指定标准信息。
1 2 3 4 |
|
输出结果如下:
1 |
|
基本格式:
1 |
|
注意看里面涉及默认值:
1 2 3 4 5 |
|
输出结果:
1 2 |
|
Python加载模块时,会生成__annotations__
,但是Python解释器自身不使用这些信息。
注解信息可以用于
- 类型检查
- 供IDE显示函数期待的类型
- 函数重载
- RPC参数
以及为参数和返回值生成文档。
PEP 484 引入类型提示(Type Hints)
- https://peps.python.org/pep-0484/
- https://peps.python.org/pep-0483/
PEP 3107 定义了函数标注的语法,但却未定义语义。 PEP 484 明确Python仍然是是一种动态语言,不会降至成静态语言。PEP 483 引入了 typing 模块,区分type与class概念。
类型提示的用法:
1 2 3 4 |
|
输出
1 2 |
|
注意,类型提示,只是提示。Python作为动态类型语言,不会进行运行时检查!!
下面的写法对Python来说没有问题:
1 2 3 4 5 |
|
结果:
1 2 |
|
尽管如此,类型提示对IDE,以及静态代码分析工具比如 mypy 都比较有用。而且PEP484似乎就是受mypy的启发而诞生的。
typing模块
Python3.5 增加typing模块,该模块主要提供对类型提示的支持。这个模块变化很大...
- typing中定义了内置类型的别名,比如 Dict,List、Set、ForzenSet、Tuple,【已经废弃
- typing中定义了collections中类型的别名,比如DefaultDict、OrderedDict、ChainMap、Counter、Deque ,【已经废弃】
- typing中定义的 Text,作为str别名,【已经废弃】
- typing中定义的 collections.abc中类型的别名,AbsractSet、Collections、Container、itemsView、KeysView、Mapping、MappingView,.... 【已经弃用】
- ...
感觉:能不用typing实现同样的效果,就不用typing。
可接受的类型提示
- 内置的类(buit-in classes),含标准库以及第三方扩展库
- 抽象基类
- types中的类型
- 用户定义的类
除此之外
- None、Any、Union、tuple、Callable
- typing中的具体类,比如Sequence、Dict
- 类型变量
- 类型别名
作为类型提示时,None等同于type(None)。
TypeVar
类型变量这个怎么理解?
如下例子,运行没问题:
1 2 3 4 5 6 7 8 9 10 |
|
使用mypy进行检查,结果(最后一行不过):
1 2 |
|
这个和Union很像,但是Union不要求参数都一样。
比如,上面代码中,T改为如下Union,mypy检查将没问题:
1 |
|
overload
同样,overload装饰器,并不是真的重载,只是用于代码检查,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
运行结果:
1 2 3 |
|
如果使用mypy进行检查,结果:
1 2 |
|
生成器
生成器的类型提示:Generator[yield_type, send_type, return_type]
1 2 3 4 5 6 7 8 9 10 |
|
PEP 526 变量注解
- https://peps.python.org/pep-0526/
1 2 3 4 5 6 |
|
结果:
1 |
|
注意,在Python3.9之前,list需要写成下面这样(新版本中废弃):
1 2 |
|
对于类成员,写法类似:
1 2 3 4 5 6 7 |
|
只是注解!
如下例所示,Test.__annotations__
会包含my_var1
,但是运行时它不存在:
1 2 3 4 5 6 7 |
|
运行结果:
1 |
|
添加一条
1 |
|
运行报错,不存在这个属性:
1 2 3 4 5 |
|
ClassVar
用于标识类变量,还是实例变量。比如如下代码,正常运行是没问题的:
1 2 3 4 5 6 7 8 9 |
|
使用mypy进行检查,结果:
1 2 |
|
NamedTuple
这个和TypedDict看起来很像,但是有很大不同。
这是collections.namedtuple()的对应版本,不直接用作注解。
1 2 3 |
|
相当于:
1 |
|
PEP 544 静态鸭子类型 Protocol
- https://peps.python.org/pep-0544/
Protocol 这个名字,让人很晕。协议?接口?...
另外,这儿有两个概念,也不知道该怎么翻译:
- Nominal Subtyping:名义子类型?PEP 484的涉及的范畴,按类型的字面量理解。
- Structural Subtyping:结构子类型?本PEP的范畴,按照结构(行为)进行理解?
总之,手册看的晕乎乎的,还是从小例子入手。
例子
- 定义Dog和Cat类
- 定义函数walk()可以接受有walk成员的对象(鸭子类型)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
问题:def walk(animal)
如何对参数添加类型提示??
添加类型提示?
单纯添加,简单。这么就可以了?
1 2 |
|
只是太傻了,再来几个动物怎么办,不能天天改这个吧?
不过也好办,那就弄个基类吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
问题解决了,但是不符合Python的鸭子哲学
添加类型提示!
Protocol方案。
- 定义一个
接口协议
Animal,但是Dog、Cat不需要从它进行继承 - Animal 可以用作类型提示。代表 Dog 和 Cat,因为它们有Animal的同样的成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
PEP 589 TypedDict
- https://peps.python.org/pep-0589/
PEP 589引入TypedDict类型,可以为不同的键设置不同的值类型提示。
1 2 3 4 5 6 7 8 |
|
以上代码执行没问题,但无法通过mypy的检查
1 2 |
|
NotRequired
- https://peps.python.org/pep-0655/
PEP 589没有未TypedDict定义可选项,PEP655完善这部分内容:
NotRequired 用于定义某个字段是不是必须的。注意,这个和可选的Optional不同,Optional只是指可以为None。
1 2 3 4 |
|
如果全都非必须,可以设置total=False
:
1 2 3 |
|
PEP 539 灵活的函数与变量注解
- https://peps.python.org/pep-0593
PEP 539 引入一个机制,将 PEP 484的类型标注扩展到任意的元数据(metadata)。
Python 3.9 增加新的类型 Annotated。比如,类型T用一个元数据x进行注解,Annotated[T, x]
。
例子:
1 2 3 4 5 6 7 8 |
|
结果:
1 2 |
|
注解与metadata
1 2 3 4 5 6 |
|
结果:
1 2 |
|
从T2看出,嵌套的注解会被展平。也就是,下面是成立的:
1 |
|
但是,要注意顺序,下面的不成立:
1 |
|
get_type_hints()
代码:
1 2 3 4 5 6 7 8 |
|
结果:
1 2 |
|
另外Python3.10引入的get_annotations()
可以取代__annotations__
:
1 2 3 |
|
带有类型参数的类型
有些容器可以包含其他值,比如dict、list、set、tuple。
容器类型
从Python3.9起,它们在typing中对应的Dict,List、Set、Tuple已经不建议使用。
比如,由str构成的list:
1 |
|
由三个元素构成的tuple:
1 |
|
由bytes构成的set:
1 |
|
dict需要指定键和值的类型,比如键为str,值为float:
1 |
|
Union
Python3.9 不需要导入typing中的 Union类型,直接使用
|
即可。
1 |
|
Optional
typing中的 Optional 也没必要用了,直接用Union即可。
Optional 会给人一个值可选的感觉,但实际上它只意味着可以为None。
比如
1 2 |
|
等同于
1 |
|
参考
- https://peps.python.org/pep-3107/
- https://peps.python.org/pep-0483/
- https://peps.python.org/pep-0484/
- https://peps.python.org/pep-0526/
- https://peps.python.org/pep-0544/
- https://peps.python.org/pep-0589/
- https://peps.python.org/pep-0593
- https://peps.python.org/pep-0655/
- https://docs.python.org/zh-cn/3/howto/annotations.html
- https://docs.python.org/zh-cn/3/library/typing.html
- https://medium.com/@life-is-short-so-enjoy-it/research-about-python-typing-annotated-95c9093f97c3
- https://so1n.me/2020/06/03/Python%E7%9A%84TypeHints/