Python的TypeHints
前记
现在,已经不是那个坚信动态语言+好用的工具就能写工程项目的时代,新出的语言都是走静态语言的路线,动态语言也都开始引入类型增强,解决自己的不足,而Python的也有自己的类型增强方案–TypeHints.
一开始用着Python写起代码非常爽,但是在大项目且多人合作时,会发现重构代码非常艰难或者不敢去修改历史代码.在引入TypeHints后,通过IDE的增强或者mypy等检查,能使我们重构代码方便,且让代码健壮.(也许有朝一日还能做到静态编译,提升性能- -…)
1.PEP的历史进程
Python作为一个非常灵活的动态语言,也不是一下子就拥有TypeHints的,而是通过PEP 3107, PEP 483, PEP 484,PEP 526,PEP 544,PEP 586,PEP 589, PEP 591,慢慢的使TypeHint变得完整
1.1.PEP 3107 TypeHints的主要依赖
PEP 3107,在06年就已经提出来了,是目前TypeHints的主要依赖,然,而此方案一开始跟TypeHints并没有什么关系,官方对于这个提案处于放养阶段,只是让函数拥有了注解功能:
1 |
|
1.2.PEP 483
PEP 483并没有去讲怎么实现TypeHints,而是简明扼要的写清楚 Python 的类型系统建设方向、边界.理清Type是语法分析的概念,class
是运行时概念,class
都是一个type,但type不一定是class.
同时PEP 483还介绍了一些常用的基础类型Any, Callabel,Optional, Tuple等,同时还支持泛型,也支持使用注释标记类型,防止被循环引用
1 |
|
1.3.PEP484
PEP 484是TypeHints的核心,首先它确定了Python仍将是一种动态类型语言,并不强制Python成为静态语言,同时讲解了TypeHints的几个新特性:
- 为已经存在的库添加类型描述文件(.pyi),通过在同名的.pyi文件编写类或函数的type hint后,即使原文件没有编写Type Hint相关代码, 也可以被mypy或者其他工具识别.这是一个不改变原有代码就可以获得Type Hint功能的手段.
- 允许使用 @overload 进行类型重载,但是只是用于代码检查时,实际上只有未被overload装饰的函数才能真正的被使用到
- 使用typing.TYPE_CHECKING,让一些库只有在运行检查时才引入
1
2
3
4import typing
if typing.TYPE_CHECKING:
import expensive_mod1.4.PEP 526
上面一直在说的都是函数,而在PEP526后,变量和类属性都可以支持TypeHints了, 在PEP526后可以如下使用:需要注意的是,上面的代码实际上并未创建变量,而是把变量和类型存在全局的1
2
3
4
5from typing import List
test_int_list: List[int]
print(__annotations__)
{'test_int_list': typing.List[int]}__annotations__
中, 如果是类属性,
那么变量则会存在类的__annotations__
中.
1.5.PEP544
544中主要说的是通过静态鸭子类型,Python自动得知类的Type Hint类型.我们都知道Python的动态类型是动态鸭子类型,当觉得一个类看起来像鸭子,那就认为他是鸭子,PEP544也是这样,只不过把确定的结果当成TypeHint反馈给代码检查工具.如官网给的例子:
1 |
|
代码中定义了 Bucket 这种类型,并且提供了两个类成员。这两个类成员刚好是 Interator 的定义。 那么在实际使用中,就可以使用 Bucket 替换 Iterable。
1.6.PEP563
在编写树节点的时候,如果我们使用TypeHints,那我们就会碰到循环依赖的问题,而PEP563就是为了解决这个问题的,在使用PEP563后我们可以如下通过使用'变量'
来解决:
1 |
|
1.7.Python3.8 PEP 对TypeHints的增强
在Python3.8中,多了几个PEP,不过比较简单,只是对TypeHint的一个完善
PEP586非常简单,只是支持以字面量来作为类型使用, 但一般不推荐这样用:
1
2
3
4
5
6
7
8
9Literal[26]
Literal[0x1A] # Exactly equivalent to Literal[26]
Literal[-4]
Literal["hello world"]
Literal[b"hello world"]
Literal[u"hello world"]
Literal[True]
Literal[Color.RED] # Assuming Color is some enum
Literal[None]PEP589则支持对每个dict的key进行类型标注
1
2
3
4
5
6
7
8
9
10from typing import TypedDict
class Movie(TypedDict):
name: str
year: int
movie: Movie = {'name': 'Blade Runner', 'year': 1982}
# 没有PEP589之前只能如下编写, 同时不能对每个key进行检查...
from typing import Dict, Union
move_dict: Dict[str, Union[str, int]]PEP591增加 final / Final, final是一个装饰器,用于声明一个类不能被更改或者继承,而Final则是声明变量不可被修改
2.一些使用小技巧
常规的TypeHints使用就不多说了,这里只说一些平常少用又好用的…
2.1别名
在项目中,经常会有一些变量类型非常复杂,且在多处地方都会使用到,纳闷利用别名,且把别名放在项目下的types.py是个不错的注意.如下是声明一个DEMO_TYPE
的别名, 别名不需要写TypeHInts.
1 |
|
2.2TypeVar的使用
TypeVar
跟Union
的使用很像,区别是Union
返回值的类型与输入的值是可以不一样的,而TypeVar
返回值类型与输入的值类型必须一样的.当前Generic
也是跟TypeVar
一样要求返回值的类型与输入的值类型必须是一样的.Union
例子:
1 |
|
TypeVar
例子:
1 |
|
在类中除了使用TypeVar
外,也可以使用overload, 如:
1 |
|
2.3防止循环引用
上面提到了在写树节点时会遇到循环引用的情况,实际上Python还有其他解决方案:
1 |
|
2.4协变与裂变
协变: 让一个比较泛的接口可以接受一个更加具体的接口作为参数或者返回值.如把猫的类赋值给英短蓝白猫的类.
裂变: 让一个比较具体的接口的可以接受一个更加泛的接口作为参数或者返回值.如把英短蓝白猫的类赋值给猫的类
1 |
|
2.5运行时类型检查
通过@runtime_checkable
装饰器和Protoclol
可以运行时检查
1 |
|
3.总结
在Python中,TypeHints能帮我们写的代码更加健全,同时借助IDE,我们也可以非常快速的编写或者更改代码,减少我们一些开发时间.
Typehints在编写代码时,只能被IDE检查进行提示,或者被检查工具用于代码检查,在实际代码中并不会生效,但是由于Python把TypeHints的一些变量存放在相关的__annotations__
中,所以我们还是可以在运行中调用__annotations__
提取TypeHints并对参数进行类型判断或者转换,这在web中非常有用,目前有个叫Pydantic
的库就是专门处理这类应用的,非常不错.
- 本文作者:So1n
- 本文链接:http://so1n.me/2020/06/03/Python%E7%9A%84TypeHints/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!