第一次接触 java 时是无比震惊的,修改大几百行代码,编译通过直接发布至预发部署后,竟运行的无比丝滑。静态类型的绝对优势,对我的 TDD 价值观都带来了极大的冲击。
万幸 Python 虽然是动态类型语言,但经过多年的发展,类型注解已逐步成熟。刚好十月底 Python 3.11.0 发布,让我们一起看看又引入了哪些新特性呢?
TOC:
- PEP 673: Self type
- PEP 646: Variadic Generics
- PEP 675: Arbitrary Literal String Type
- PEP 655: Marking individual TypedDict items as required or potentially missing
- PEP 681: Data Class Transforms
PEP 673: Self type
痛点:15 行 set_scale
方法返回基类 Shape
,若继续调用 16 行 set_radius
方法,会导致静态类型检查器报错:提示找不到该方法。
1 | from __future__ import annotations |
解法:引入 Self
关键字规避该问题。
1 | from __future__ import annotations |
PEP 646: Variadic Generics
痛点:虽然入参已提示为 Array
类型(任意维度),但需进一步明确类型为 Array[int]
or Array[int, str, float]
1 | def add_dimension(arrar: Array): ... |
解法:新引入 TypeVarTuple
关键字代表可变长度的一坨类型(number of types),并支持使用 *
关键字展开。
1 | from __future__ import annotations |
PEP 675: Arbitrary Literal String Type
痛点:如何规避 sql 注入等问题(特别是 f-string)。
仅通过文档提示用户是远远不够的,有没有可能直接在静态检查中显性提示用户?(#6)
1 | def run_query(sql: str, *params: object) -> ...: |
解法:新引入 LiteralString
关键字,代表仅接受文字字符串类型,实现静态的注入风险异常提示(#8)
1 | from typing import LiteralString |
PEP 655: Marking individual TypedDict items as required or potentially missing
针对 PEP 589 引入的 TypedDict
,新增 Required
& NotRequired
关键字。
如下若属性 year
标记为必填,静态检查则会直接报错。
1 | from typing import Required, TypedDict |
PEP 681: Data Class Transforms
痛点:第三方库的数据类(例如 Django 中的 ORM model、attr 库等),各自提供类似 @dataclass
的语法,但静态类型解析器不可能一一适配。
1 | import attr |
解法:引入了 dataclass_transform
提供统一的“协议标准”后,自动“合成”对应的类型注解,让静态类型检查器将第三方库的数据类当作 dataclass
一样统一处理,包含:
- 自动合成
__init__
方法 - 自动合成
__eq__
,__ne__
,__lt__
等魔法方法(可选) - 支持
frozen
选项的静态解析,字段是否不可变 - 支持
field specifiers
,e.g. 字段是否提供了默认值
举个例子:
1 | from typing import dataclass_transform |
题外话
- 震惊1:pyright 项目中天天吭哧吭哧提交代码,辛勤认真回复 issue 的维护者 Eric Traut,竟然是微软的 Technical Fellow
- 震惊2:无意中搜索到,该 PEP 681 proposal 的初心是为了解决 pyright 无法正确解析 attr 库静态类型的问题(#146),而作者就是 Eric ... https://github.com/microsoft/pyright/discussions/1782