官方文档:https://docs.python.org/3/library/typing.html#module-typing
YouTube:https://www.youtube.com/watch?v=omORtPiMBN4&t=300s&ab_channel=DougMercer
为什么关心类型检查?
- 提高代码的清晰度
- 知道变量是什么对象以及它有哪些方法
- 错误更少,因为可以执行静态分析,允许在运行代码之前捕获类型错误
所以类型提示如何提高代码清晰度?
def impute(x, method, safe):
...
如果没有查看实现内容,X,method 和 safe 有什么有效值?
def impute(
x: list[int | None], method: str, safe: bool
) -> list[int]:
...
X 接受 int 和 Nones 列表。
method 是字符串。
safe 是一个布尔,也许能够启用/禁用安全检查。
所以,文档字符串还需要吗?
毕竟我们可以从文档字符串中获取所有这些信息,在一些源码中可以看到使用文档字符串一样很清晰,那么是文档字符串更好还是类型提示更好(因为它可以被程序分析)?
很好的文档字符串注释:
def impute(x, method, safe):
"""Impute missing values in time series.
Parameters
----------
x
List of integers or None values.
method
str, specifying imputation method.
safe
bool, if True perform safety check.
Returns
-------
List of integers, None values replaced with imputed values.
...
基本类型提示
age: int = 42 // 表示一个整数
weight: float = 170.5
first_name: str = "Bob"
last_name: bytes = b"Bobbington"
组成:变量名+冒号+类型+值
# 对于Python 3.9+,我们使用集合的内置类型
quantities: list[int] = [1, 7, 6, 2, 0]
places_ive_visited: set[str] = {"Maryland", "New York", "Florida"}
# 对于Python <3.9,我们导入来自键入的 typing
from typing import List, Set
quantities: List[int] = [1, 7, 6, 2, 0]
places_ive_visited: Set[str] = {"Maryland", "New York", "Florida"}
# 对于映射,我们需要两个键和值的类型
menu: dict[str, int] = {"ham sandwich": 12, "soup du jour": 4}
# 对于固定大小的元组,我们指定了所有元素的类型
x: tuple[int, str, float] = (3, "yes", 7.5)
# 对于变量大小的元组,我们使用一种类型和省略号
x: tuple[int, ...] = (1, 2, 3)
函数类型提示
# 参考上面的示例
def repeat_str(s: str, n: int) -> str:
...
# 可以使用`Callable`键入`repeat_str`的值
from typing import Callable
g: Callable[[str, int], str] = repeat_str
lambda 的类型标注
由于类型注解的语法和 lambda 的语法冲突,因此不能直接对 lambda 做类型注解,但我们可以将 lambda 传给一个变量,通过对这个变量做 lambda,达到相同的目的。以下对 lambda 的几个例子:
from typing import Callable
# is_even 传入 int 返回布尔
is_even: Callable[[int], bool] = lambda x: (x % 2 == 0)
# func 传入两个字符串,返回 int
func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2)
函数的一些高级用法
如何键入多个返回的提示函数?
def func() -> tuple[int, str]: return 1, "a">>> print(func())(1, 'a')>>> isinstance(func(), tuple)True
如果变量可以是多种类型,该怎么办?
# Python 3.10+x: int | str | None = None# Python <3.10from typing import Unionx: Union[int, str, None] = None# 特殊情况:变量是*某些东西*或没有from typing import Optionaly: Optional[int] = None
注意:如果变量可以是什么或者难以键入的情况怎么办? 谨慎使用
如果变量只应对少数值?
from typing_extensions import Literaldef li(s: str, mode: Literal["x", "xx", "d", "e"]) -> str: pass result = li(s="acascsac", mode="77") # Expected type 'Literal["x", "xx", "d", "e"]', got 'Literal["77"]' instead print(result)
如何定义一次类型并多次使用它?
from typing import TypeAliasVector: TypeAlias = list[float]def awful_vector_add(u: Vector, v: Vector) -> Vector: ...
注意:有些模块只支持 python 版本 >= 3.8, 对于小于 3.8的我建议的方法:
import sysif sys.version_info >= (3, 8): from typing import Protocolelse: from typing_extensions import Protocol, Literal
如何支持多个复杂的输入/输出签名?@overload
官方文档:https://docs.python.org/3/library/typing.html#typing.overload
overload翻译过来是“重载”的意思,Java中有这样的两个概念,重写(override)和重载(overload)。重写其实是在保证输入和输出不变的情况下重写实现逻辑。而重载则是允许修改输入和输出,即同一个方法名可以支持多种类型的输入和输出。python 中不提供函数重载,python 作为一个动态语言,天然支持可变参数类型和可变参数个数。
因此有两种方案:
1. typing.TypeVar2. typing.overload
第一种:对于固定数量参数的方法而言,同一个参数如果打算接受多种类型,可以这么用,比方说参数可以是:int, float, str:
import typingT = typing.TypeVar('T', int, float, str)def foo(name: T) -> str: return 'hello ' + str(name)foo(2)这种方案更类似于静态语言中的interface的概念,定义一个通用的父类,这样的话,你可以传递子类型过去。
第二种:也就是重载的方式。但是跟静态语言中还是很有差别。
import typing@typing.overloaddef foo(name: str) -> str: ...@typing.overloaddef foo(name: float) -> float: ...@typing.overloaddef foo(name: int, age: int) -> str: ...def foo(name, age=18): return 'hello ' + str(name)foo(2)通过定义多个同名函数,上面的同名函数需要通过 overload 装饰器装饰。可以看到被装饰的函数的输入类型和输出类型都可以更改。但是,最后的实现方法一定要通用,也就是没有类型注解。被overload装饰的函数仅仅是为了受益于类型检查工具,因为它们会被没有overload装饰的函数定义覆盖,尽管未被装饰的函数是用于运行时的,但是会被类型检查工具忽略。
如何确保输入/返回类型匹配给定多个输入类型?
from typing import TypeVarT = TypeVar("T", int, float, list[int], list[float])U = int | float | list[int] | list[float]def f(x: T) -> T: ...def g(x: U) -> U: ...(f(1)) # int*(f(1.5)) # float*(f([1])) # list*[int](g(1)) # Union[int, float, list[int], list[float]](g(1.5)) # Union[int, float, list[int], list[float]](g([1])) # Union[int, float, list[int], list[float]]
如何制作一个支持多个输入类型的容器(例如,列出[int])?
from typing import Generic, TypeVarT = TypeVar("T")class Thing(Generic[T]): def __init__(self, x: T) -> None: self.x: T = x def func(self) -> T: return self.xreveal_type(Thing(1)) # Thing[builtins.int*]reveal_type(Thing(1.5)) # Thing[builtins.float*]
使用 mypy 进行类型检查
我们可以使用 pip
来安装mypy:
pip install mypy
test.py:
from typing_extensions import Literaldef li(s: str, mode: Literal["x", "xx", "d", "e"]) -> str: passresult = li(s="xx", mode="xx") # Expected type 'Literal["x", "xx", "d", "e"]', got 'Literal["77"]' insteadprint(result)
命令:mypy 文件路径
D:\test\manytable>mypy type_check.pySuccess: no issues found in 1 source fileD:\test\manytable>mypy type_check.pytype_check.py:8: error: Argument "s" to "li" has incompatible type "int"; expected "str"Found 1 error in 1 file (checked 1 source file)