有时几个变量的类型是相关的,比如 "如果x是A型,y是B型,否则y是C型"。 基本的类型提示不能描述这种关系,使类型检查变得麻烦或不准确。 我们可以用 @typing.overload
来正确表示类型关系。
以这个函数为例。
from __future__ import annotations
def double(input_: int | list[int]) -> int | list[int]:
if isinstance(input_, list):
return [i * 2 for i in input_]
return input_ * 2
变量有这些类型关系。
- 如果
input_
是一个int
,返回值是一个int
。 - 如果
input_
是一个list[int]
,那么返回值也是一个list[int]
。
int
只有这些组合是可能的。不可能input_
,而返回值是list[int]
,反之亦然。但目前的类型提示并没有捕捉到这种关系。
x = double(12)
reveal_type(x)
Mypy输出。
$ mypy example.py
example.py:11: note: Revealed type is 'Union[builtins.int, builtins.list[builtins.int]]'
输入是一个int
,但是Mypy发现它把x
的类型看成是int | list[int]
(在旧的长式拼写中)。任何试图使用int
- only operations withx
,比如除法,都会导致类型检查失败。为了修复这种错误,我们将被迫使用类型缩小。
我们可以重写double
的提示,用 @typing.overload
来表示类型关系。
from __future__ import annotations
from typing import overload
@overload
def double(input_: int) -> int:
...
@overload
def double(input_: list[int]) -> list[int]:
...
def double(input_: int | list[int]) -> int | list[int]:
if isinstance(input_, list):
return [i * 2 for i in input_]
return input_ * 2
这乍一看有点奇怪--我们对double
进行了三次定义!让我们把它拆开。
前两个@overload
定义只为它们的类型提示而存在。每个定义代表一个允许的类型组合。这些定义从不运行,所以它们的主体可以包含任何东西,但使用 Python 的...
(省略号) 字样是很习惯的。
第三个定义是实际的实现。 在这种情况下,我们需要提供类型提示,联合每个变量的所有可能的类型。 如果没有这样的提示,Mypy 将跳过对函数体的类型检查。
当 Mypy 检查文件时,它收集了@overload
定义作为类型提示。然后它使用第一个非@overload
定义作为实现。所有@overload
定义必须在实现之前,不允许有多个实现。
当 Python 导入文件时,@overload
定义会创建临时的double
函数,但每个定义都会被下一个定义覆盖。在导入后,只有实现存在。作为防止意外丢失实现的保护措施,试图调用@overload
定义会引发NotImplementedError
。
有了我们的类型关系描述,让我们检查一下两种输入类型的返回类型。
x = double(12)
reveal_type(x)
y = double([1, 2])
reveal_type(y)
Mypy说。
$ mypy example.py
example.py:23: note: Revealed type is 'builtins.int'
example.py:26: note: Revealed type is 'builtins.list[builtins.int]'
很好!返回类型与输入类型相匹配,正如我们所希望的那样。现在可以对double()
的任何调用者进行准确的类型检查,不需要任何额外的缩小。
@overload
关于更多的例子,请看Mypy文档中的函数重载部分。
Fin
可能类型提示永远不会让你重载。
-Adam