本文记录在 Python 中 Typing 库提供的一些功能,以此来更好的定义日常开发中的类型,当有类型时,我们可以更好的知道某些 API 的输入输出的类型,利人利己。
Type Aliases
new in v3.10
在 Python 中某些类型会比较复杂,我们可以对其做一个类型别名, 起到简化的作用。 同时在创建这样的一个别名的同时,我们可以对这个新的别名设置类型TypeAlias
from typing import TypeAlias
Vector: TypeAlias = list[float]
NewType
顾名思义,这会创建一个新的类型,同时支持从这新的类型创建另外一个新的类型,不过不支持将这个类型作为类的父类。
from typing import NewType
BookId = NewType('BookId', int)
book_id = BookId(1234)
type(book_id) # <class 'int'>
new_book_id = BookId(1) + BookId(2)
# new_book_id type is int, not BookId
当我们定义了新的类型后,如果入参和期望的类型不一致时,会有 warning,但是不影响 runtime。
def query_book_info(book_id: BookId) -> dict:
...
book_id = BookId(1234)
_ = query_book_info(book_id) # static type checker feels good
_ = query_book_info(1234) # static type checker complain about the type int not BookId
另外一些注意
SubBookId = NewType('SubBookId', BookId) # correct
_ = query_book_info(SubBookId(123)) # static type still complain about that
# 不可作为继承使用
class SubBookId(BookId): # invalid !
pass
Callable
基本结构为Callable[[Arg1Type, Arg2Type], ReturnType]
Generics
Python 中范型的定义比较简单,比如:
from collections.abc import Sequence
from typing import TypeVar
T = TypeVar('T')
def first(l: Sequence[T]) -> T:
...
Annotating tuples
正常我们在定义 list 等容器类型时,只能定义一种类型,如果我们的数据里面有不同的类型,这个时候就可以使用 annotating tuples 的类型了
x: list[int] = []
x: tuple[int, str] = (1, 'foo') # 可以是不同的类型的结果
x: tuple[int, ...] # 表示 x 可是任意长度的 int 类型
Typing Module Content
AnyStr
这个类型表示可以接受的参数类型是str
或者bytes
。 但是注意在同时使用多个的时候,需要注意入参要保持一致。比如:
def show_content(ctx1: AnyStr, ctx2: AnyStr) -> None
show_content(b'abc', 'def') # static type checker complain about that, error, cannot mix str and bytes
LiteralString
new in v3.11
所有字符串字面量都是LiteralString
, 但是str
不是LiteralString
Never
new in v3.11
通常用来定义永远不会被调用或函数不会返回的那种
def never_call_me(arg: Never) -> None:
...
never_call_me(_) # ok
never_call_me('abc') # type checker error
在低版本中我们定义无返回可以使用NoReturn
。
Self
new in v3.11
顾名思义,我们可以在classmethod
中使用。
Special forms
Union
表示可以同时可以接受的类型,在 v3.10 中可以使用|
代替,有几个规则
Union[str, str ] ==> str
Union[int, str, [float, dict]] ==> Union[int, str, float, dict]
为了兼容低版本,可以使用 Union,|
在自己的脚本中使用即可。
Optional
可以理解成是 Union[X, None], 不过当参数需要显式的负值为 None 的时候,使用 Optional 比较合适。
def foo(arg: Optional[int] = None) -> None:
...
Callable
Callable[InputTypes, ReturnTypes]
用来定义函数或者方法的,一般结合Concatenate
和ParamSpec
使用。
Concatenate
new in v3.10
Concatenate 当前只能用在 Callable 的第一个参数中,一般我们结合ParamSpec
使用。更多的时候是创建一个 decorator 的时候。
比如我们函数接受多个参数,其中某个参数是需要明确给出变量定义的,而其他的参数可以被归类成一组,这个时候我们可以这么定义函数的类型
def my_func(arg1: str, arg2: int, arg3: dict) -> int:
...
可以通过如下的方式定义这个函数的类型
P = ParamSpec('P')
R = TypeVar('R')
Callable[Concatenate[str, P], R]
现在我们可以这样定义个 decorator
def my_dec(f: Callable[Concatenate[str, P], R]) -> Callable[P, R]:
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
return f('hello', *args, **kwargs)
return inner
@my_dec
def my_func(arg1: str, arg2: int, arg3:dict) -> int:
...
# arg1 is pass in decorator
my_func(arg2, arg3)
Literial
正常情况下来表示该参数是一组字面量,且是定义中提供的字面量。 一般类型检查器会对其做校验。
mode = Literial['r', 'rb', 'w', 'wb']
ClassVar
顾名思义,用于标记 class variable,可以在dataclasses
中使用。
Final
通过 Final 标记的变量在任何时候都不能被赋值(runtime时不影响)。
MAX_SIZE: Final = 9000
MAX_SIZE += 1 # tpye checker report error