typing —— 对类型提示的支持

Added in version 3.5.

源代码: Lib/typing.py [https://github.com/python/cpython/tree/3.13/Lib/typing.py]

备注

Python 运行时不强制要求函数与变量类型标注。 它们可被 类型检查器、IDE、语法检查器等第三方工具使用。


本模块提供了对类型提示的运行时支持。

考虑下面的函数:

  1. def surface_area_of_cube(edge_length: float) -> str:
  2. return f"The surface area of the cube is {6 * edge_length ** 2}."

函数 surface_area_of_cube 接受一个预期为 float 实例的参数,如 type hint edge_length: float 所指明的。 该函数预期返回一个 str 实例,如 -> str 提示所指明的。

类型提示可以是简单的类比如 floatstr,它们也可以更为复杂。 typing 模块提供了一套用于更高级类型提示的词汇。

新特性被频繁添加到 typing 模块中。 typing_extensions [https://pypi.org/project/typing_extensions/] 包提供了这些新特性针对较旧版本 Python 的向下移植。

参见

有关 Python 类型系统的规范说明

Python 类型系统最新的规范说明可以在 "Specification for the Python type system" [https://typing.readthedocs.io/en/latest/spec/index.html] 查看。

类型别名

类型别名是使用 type 语句来定义的,它将创建一个 TypeAliasType 的实例。 在这个示例中,Vectorlist[float] 将被静态类型检查器等同处理:

  1. type Vector = list[float]
  2.  
  3. def scale(scalar: float, vector: Vector) -> Vector:
  4. return [scalar * num for num in vector]
  5.  
  6. # 通过类型检查;浮点数列表是合格的 Vector。
  7. new_vector = scale(2.0, [1.0, -4.2, 5.4])

类型别名适用于简化复杂的类型签名。例如:

  1. from collections.abc import Sequence
  2.  
  3. type ConnectionOptions = dict[str, str]
  4. type Address = tuple[str, int]
  5. type Server = tuple[Address, ConnectionOptions]
  6.  
  7. def broadcast_message(message: str, servers: Sequence[Server]) -> None:
  8. ...
  9.  
  10. # 静态类型检查器会认为上面的类型签名
  11. # 完全等价于下面这个写法。
  12. def broadcast_message(
  13. message: str,
  14. servers: Sequence[tuple[tuple[str, int], dict[str, str]]]
  15. ) -> None:
  16. ...

type 语句是在 Python 3.12 中新增加的。 为了向下兼容,类型别名也可以通过简单的赋值来创建:

  1. Vector = list[float]

或者用 TypeAlias 标记来显式说明这是一个类型别名,而非一般的变量赋值:

  1. from typing import TypeAlias
  2.  
  3. Vector: TypeAlias = list[float]

NewType

NewType 助手创建与原类型不同的类型:

  1. from typing import NewType
  2.  
  3. UserId = NewType('UserId', int)
  4. some_id = UserId(524313)

静态类型检查器把新类型当作原始类型的子类,这种方式适用于捕捉逻辑错误:

  1. def get_user_name(user_id: UserId) -> str:
  2. ...
  3.  
  4. # 通过类型检查
  5. user_a = get_user_name(UserId(42351))
  6.  
  7. # 未通过类型检查;整数不能作为 UserId
  8. user_b = get_user_name(-1)

UserId 类型的变量可执行所有 int 操作,但返回结果都是 int 类型。这种方式允许在预期 int 时传入 UserId,还能防止意外创建无效的 UserId

  1. # 'output' 的类型为 'int' 而非 'UserId'
  2. output = UserId(23413) + UserId(54341)

注意,这些检查只由静态类型检查器强制执行。在运行时,语句 Derived = NewType('Derived', Base) 将产生一个 Derived 可调用对象,该对象立即返回你传递给它的任何参数。 这意味着语句 Derived(some_value) 不会创建一个新的类,也不会引入超出常规函数调用的很多开销。

更确切地说,在运行时,some_value is Derived(some_value) 表达式总为 True。

创建 Derived 的子类型是无效的:

  1. from typing import NewType
  2.  
  3. UserId = NewType('UserId', int)
  4.  
  5. # 将在运行时失败且无法通过类型检查
  6. class AdminUserId(UserId): pass

然而,我们可以在 "派生的" NewType 的基础上创建一个 NewType

  1. from typing import NewType
  2.  
  3. UserId = NewType('UserId', int)
  4.  
  5. ProUserId = NewType('ProUserId', UserId)

同时,ProUserId 的类型检查也可以按预期执行。

详见 PEP 484 [https://peps.python.org/pep-0484/]。

备注

请记住使用类型别名将声明两个类型是相互 等价 的。 使用 type Alias = Original 将使静态类型检查器在任何情况下都把 Alias 视为与 Original 完全等价。 这在你想要简化复杂的类型签名时会很有用处。

反之,NewType 声明把一种类型当作另一种类型的 子类型Derived = NewType('Derived', Original) 时,静态类型检查器把 Derived 当作 Original子类 ,即,Original 类型的值不能用在预期 Derived 类型的位置。这种方式适用于以最小运行时成本防止逻辑错误。

Added in version 3.5.2.

在 3.10 版本发生变更: NewType 现在是一个类而不是一个函数。 因此,当调用 NewType 而非常规函数时会有一些额外的运行时开销。

在 3.11 版本发生变更: 调用 NewType 的性能已恢复到 Python 3.9 时的水平。

标注可调用对象

函数 — 或是其他 callable 对象 — 可以使用 collections.abc.Callable 或已被弃用的 typing.Callable 来标注。 Callable[[int], str] 表示一个接受 int 类型的单个形参并返回一个 str 的函数。

例如:

  1. from collections.abc import Callable, Awaitable
  2.  
  3. def feeder(get_next_item: Callable[[], str]) -> None:
  4. ... # 函数体
  5.  
  6. def async_query(on_success: Callable[[int], None],
  7. on_error: Callable[[int, Exception], None]) -> None:
  8. ... # 函数体
  9.  
  10. async def on_update(value: str) -> None:
  11. ... # 函数体
  12.  
  13. callback: Callable[[str], Awaitable[None]] = on_update

下标语法总是要刚好使用两个值:参数列表和返回类型。 参数列表必须是一个由类型组成的列表、ParamSpecConcatenate 或省略号。 返回类型必须是单一类型。

如果将一个省略号字面值 作为参数列表,则表示可以接受包含任意形参列表的可调用对象:

  1. def concat(x: str, y: str) -> str:
  2. return x + y
  3.  
  4. x: Callable[..., str]
  5. x = str # 可以
  6. x = concat # 同样可以

Callable 无法表达复杂的签名如接受可变数量参数的函数,重载的函数,或具有仅限关键字形参的函数。 但是,这些签名可通过自定义具有 __call__() 方法的 Protocol 类来表达:

  1. from collections.abc import Iterable
  2. from typing import Protocol
  3.  
  4. class Combiner(Protocol):
  5. def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ...
  6.  
  7. def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes:
  8. for item in data:
  9. ...
  10.  
  11. def good_cb(*vals: bytes, maxlen: int | None = None) -> list[bytes]:
  12. ...
  13. def bad_cb(*vals: bytes, maxitems: int | None) -> list[bytes]:
  14. ...
  15.  
  16. batch_proc([], good_cb) # 可以
  17. batch_proc([], bad_cb) # 错误!参数 2 的类型不兼容
  18. # 因为在回调中有不同的名称和类别

以其他可调用对象为参数的可调用对象可以使用 ParamSpec 来表明其参数类型是相互依赖的。 此外,如果该可调用对象增加或删除了其他可调用对象的参数,可以使用 Concatenate 操作符。 它们分别采取 Callable[ParamSpecVariable, ReturnType]Callable[Concatenate[Arg1Type, Arg2Type, …, ParamSpecVariable], ReturnType] 的形式。

在 3.10 版本发生变更: Callable 现在支持 ParamSpecConcatenate。 详情见 PEP 612 [https://peps.python.org/pep-0612/]。

参见

ParamSpecConcatenate 的文档提供了在 Callable 中使用的例子。

泛型(Generic)

由于无法以通用方式静态地推断容器中保存的对象的类型信息,标准库中的许多容器类都支持下标操作来以表示容器元素的预期类型。

  1. from collections.abc import Mapping, Sequence
  2.  
  3. class Employee: ...
  4.  
  5. # Sequence[Employee] 表明该序列中的所有元素
  6. # 都必须是 "Employee" 的实例。
  7. # Mapping[str, str] 表明该映射中的所有键和所有值
  8. # 都必须是字符串。
  9. def notify_by_email(employees: Sequence[Employee],
  10. overrides: Mapping[str, str]) -> None: ...

泛型函数和类可以通过使用 类型形参语法 来实现参数化:

  1. from collections.abc import Sequence
  2.  
  3. def first[T](l: Sequence[T]) -> T: # 函数是 TypeVar "T" 泛型
  4. return l[0]

或直接使用 TypeVar 工厂:

  1. from collections.abc import Sequence
  2. from typing import TypeVar
  3.  
  4. U = TypeVar('U') # 声明类型变量 "U"
  5.  
  6. def second(l: Sequence[U]) -> U: # 函数是 TypeVar "U" 泛型
  7. return l[1]

在 3.12 版本发生变更: 对泛型的语法支持是在 Python 3.12 中新增的。

标注元组

对于 Python 中的大多数容器,类型系统会假定容器中的所有元素都是相同类型的。 例如:

  1. from collections.abc import Mapping
  2.  
  3. # 类型检查器将推断 ``x`` 中的所有元素均为整数
  4. x: list[int] = []
  5.  
  6. # 类型检查器错误: ``list`` 只接受单个类型参数:
  7. y: list[int, str] = [1, 'foo']
  8.  
  9. # 类型检查器将推断 ``z`` 中的所有键均为字符串,
  10. # 并且 ``z`` 中的所有值均为字符串或整数
  11. z: Mapping[str, str | int] = {}

list 只接受一个类型参数,因此类型检查器将在上述代码中对 y 赋值时报告错误。同样,Mapping 只接受两个类型参数:第一个给出键的类型,第二个则给出值的类型。

然而,与大多数其它 Python 容器不同的是,在常见的 Python 代码中,元组中元素的类型并不相同。因此,在 Python 的类型系统中,元组是特殊情况。tuple 可以接受 任意数量 的类型参数:

  1. # 可以: ``x`` 被赋值为长度为 1 的元组,其中的唯一元素是个整数
  2. x: tuple[int] = (5,)
  3.  
  4. # 可以: ``y`` 被赋值为长度为 2 的元素;
  5. # 第 1 个元素是个整数,第 2 个元素是个字符串
  6. y: tuple[int, str] = (5, "foo")
  7.  
  8. # 错误: 类型标注表明是长度为 1 的元组,
  9. # 但 ``z`` 却被赋值为长度为 3 的元组
  10. z: tuple[int] = (1, 2, 3)

要表示一个可以是 任意 长度的元组,并且其中的所有元素都是相同类型的 T,请使用 tuple[T, …]。要表示空元组,请使用 tuple[()]。只使用 tuple 作为注解等效于使用tuple[Any, ...]

  1. x: tuple[int, ...] = (1, 2)
  2. # 这些赋值是可以的 OK: ``tuple[int, ...]`` 表明 x 可以为任意长度
  3. x = (1, 2, 3)
  4. x = ()
  5. # 这个赋值是错误的: ``x`` 中的所有元素都必须为整数
  6. x = ("foo", "bar")
  7.  
  8. # ``y`` 只能被赋值为一个空元组
  9. y: tuple[()] = ()
  10.  
  11. z: tuple = ("foo", "bar")
  12. # 这些重新赋值是可以的 OK: 简单的 ``tuple`` 等价于 ``tuple[Any, ...]``
  13. z = (1, 2, 3)
  14. z = ()

类对象的类型

带有 C 标注的变量可接受 C 类型的值。 反之,带有 type[C] (或已被弃用的 typing.Type[C]) 标注的变量则可接受本身是类的值 — 准确地说,它将接受 C类对象。 例如:

  1. a = 3 # 为 ``int`` 类型
  2. b = int # 为 ``type[int]`` 类型
  3. c = type(a) # 同样为 ``type[int]`` 类型

注意,type[C] 是协变的:

  1. class User: ...
  2. class ProUser(User): ...
  3. class TeamUser(User): ...
  4.  
  5. def make_new_user(user_class: type[User]) -> User:
  6. # ...
  7. return user_class()
  8.  
  9. make_new_user(User) # 可以
  10. make_new_user(ProUser) # 同样可以: ``type[ProUser]`` 是 ``type[User]`` 的子类型
  11. make_new_user(TeamUser) # 仍然可以
  12. make_new_user(User()) # 错误: 预期为 ``type[User]`` 但得到 ``User``
  13. make_new_user(int) # 错误: ``type[int]`` 不是 ``type[User]`` 的子类型

type 的合法形参只有类, Any, 类型变量 以及前面这些类型的并集。 例如:

  1. def new_non_team_user(user_class: type[BasicUser | ProUser]): ...
  2.  
  3. new_non_team_user(BasicUser) # 可以
  4. new_non_team_user(ProUser) # 可以
  5. new_non_team_user(TeamUser) # 错误: ``type[TeamUser]`` 不是
  6. # ``type[BasicUser | ProUser]`` 的子类型
  7. new_non_team_user(User) # 同样错误

type[Any] 等价于 type,它是 Python 的 元类层级结构 的根对象。

标注生成器和协程

生成器可以使用泛型类型 Generator[YieldType, SendType, ReturnType] 来标。 例如:

  1. def echo_round() -> Generator[int, float, str]:
  2. sent = yield 0
  3. while sent >= 0:
  4. sent = yield round(sent)
  5. return 'Done'

请注意与标准库里的许多其他泛型类不同,GeneratorSendType 采用逆变行为,而不是协变或不变行为。

SendTypeReturnType 形参默认为 None

  1. def infinite_stream(start: int) -> Generator[int]:
  2. while True:
  3. yield start
  4. start += 1

也可以显式设置这些类型:

  1. def infinite_stream(start: int) -> Generator[int, None, None]:
  2. while True:
  3. yield start
  4. start += 1

仅产生值的简单生成器可以被标注为具有 Iterable[YieldType]Iterator[YieldType] 类型的返回值:

  1. def infinite_stream(start: int) -> Iterator[int]:
  2. while True:
  3. yield start
  4. start += 1

异步生成器的处理方式类似,但不要指望有 ReturnType 类型参数 (AsyncGenerator[YieldType, SendType])。 SendType 参数默认为 None,因此以下定义是等价的:

  1. async def infinite_stream(start: int) -> AsyncGenerator[int]:
  2. while True:
  3. yield start
  4. start = await increment(start)
  5.  
  6. async def infinite_stream(start: int) -> AsyncGenerator[int, None]:
  7. while True:
  8. yield start
  9. start = await increment(start)

与同步情况一样,AsyncIterable[YieldType]AsyncIterator[YieldType] 也可用:

  1. async def infinite_stream(start: int) -> AsyncIterator[int]:
  2. while True:
  3. yield start
  4. start = await increment(start)

协程可使用 [YieldType, SendType, ReturnType] 进行注释。 泛型参数对应于 Generator 的参数,例如:

  1. from collections.abc import Coroutine
  2. c: Coroutine[list[str], str, int] # 在其他地方定义的协程
  3. x = c.send('hi') # 推断 'x' 的类型为 list[str]
  4. async def bar() -> None:
  5. y = await c # 推断 'y' 的类型为 int

用户定义的泛型类型

用户定义的类可以定义为泛型类。

  1. from logging import Logger
  2.  
  3. class LoggedVar[T]:
  4. def __init__(self, value: T, name: str, logger: Logger) -> None:
  5. self.name = name
  6. self.logger = logger
  7. self.value = value
  8.  
  9. def set(self, new: T) -> None:
  10. self.log('Set ' + repr(self.value))
  11. self.value = new
  12.  
  13. def get(self) -> T:
  14. self.log('Get ' + repr(self.value))
  15. return self.value
  16.  
  17. def log(self, message: str) -> None:
  18. self.logger.info('%s: %s', self.name, message)

这种语法表示类 LoggedVar 是围绕单个 类型变量 T 实现参数化的。 这也使得 T 成为类体内部有效的类型。

泛型类隐式继承自 Generic。为了与 Python 3.11 及更低版本兼容,也允许显式地从 Generic 继承以表示泛型类:

  1. from typing import TypeVar, Generic
  2.  
  3. T = TypeVar('T')
  4.  
  5. class LoggedVar(Generic[T]):
  6. ...

泛型类具有 __class_getitem__() 方法,这意味着泛型类可在运行时进行参数化(例如下面的 LoggedVar[int]):

  1. from collections.abc import Iterable
  2.  
  3. def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
  4. for var in vars:
  5. var.set(0)

一个泛型可以有任何数量的类型变量。所有种类的 TypeVar 都可以作为泛型的参数:

  1. from typing import TypeVar, Generic, Sequence
  2.  
  3. class WeirdTrio[T, B: Sequence[bytes], S: (int, str)]:
  4. ...
  5.  
  6. OldT = TypeVar('OldT', contravariant=True)
  7. OldB = TypeVar('OldB', bound=Sequence[bytes], covariant=True)
  8. OldS = TypeVar('OldS', int, str)
  9.  
  10. class OldWeirdTrio(Generic[OldT, OldB, OldS]):
  11. ...

Generic 类型变量的参数应各不相同。下列代码就是无效的:

  1. from typing import TypeVar, Generic
  2. ...
  3.  
  4. class Pair[M, M]: # SyntaxError
  5. ...
  6.  
  7. T = TypeVar('T')
  8.  
  9. class Pair(Generic[T, T]): # 无效
  10. ...

泛型类也可以从其他类继承:

  1. from collections.abc import Sized
  2.  
  3. class LinkedList[T](Sized):
  4. ...

从泛型类继承时,某些类型参数可被固定:

  1. from collections.abc import Mapping
  2.  
  3. class MyDict[T](Mapping[str, T]):
  4. ...

在这个例子中,MyDict 就只有一个参数 T

未指定泛型类的类型参数时,会假定每个位置的类型都为 Any。在下面的例子中,MyIterable 不是泛型,但却隐式继承了 Iterable[Any]

  1. from collections.abc import Iterable
  2.  
  3. class MyIterable(Iterable): # 与 Iterable[Any] 相同
  4. ...

用户定义的泛型类型别名也同样受到支持。例如:

  1. from collections.abc import Iterable
  2.  
  3. type Response[S] = Iterable[S] | int
  4.  
  5. # 这里的返回类型与 Iterable[str] | int 相同
  6. def response(query: str) -> Response[str]:
  7. ...
  8.  
  9. type Vec[T] = Iterable[tuple[T, T]]
  10.  
  11. def inproduct[T: (int, float, complex)](v: Vec[T]) -> T: # 与 Iterable[tuple[T, T]] 相同
  12. return sum(x*y for x, y in v)

出于向后兼容性的考虑,也允许使用简单的赋值来创建泛型类型别名:

  1. from collections.abc import Iterable
  2. from typing import TypeVar
  3.  
  4. S = TypeVar("S")
  5. Response = Iterable[S] | int

在 3.7 版本发生变更: Generic 不再支持自定义元类。

在 3.12 版本发生变更: 3.12 版本新增了对泛型和类型别名的语法支持。在之前的版本中,泛型类必须显式继承自 Generic,或者在其基类之一中包含有类型变量。

用户定义的参数表达式的泛型也受到支持,可以采用 [**P] 形式的参数规格变量来表示。该行为与上面描述的类型变量一致,因为参数规格变量被 typing 模块视为专门的类型变量。这方面的一个例外是,类型的列表可用于替代 ParamSpec

  1. >>> class Z[T, **P]: ... # T 为 TypeVar;P 为 ParamSpec
  2. ...
  3. >>> Z[int, [dict, float]]
  4. __main__.Z[int, [dict, float]]

带有 ParamSpec 的泛型类也可以使用从 Generic 显式继承的方式来创建。在这种情况下,不需要使用 **

  1. from typing import ParamSpec, Generic
  2.  
  3. P = ParamSpec('P')
  4.  
  5. class Z(Generic[P]):
  6. ...

TypeVarParamSpec 的另一个区别在于只有单个参数规格变量的泛型会接受形如 X[[Type1, Type2, …]] 的参数列表,同时为了美观,也接受 X[Type1, Type2, …] 这样的形式。 在内部,后者被转换为前者,所以下面的内容是等价的:

  1. >>> class X[**P]: ...
  2. ...
  3. >>> X[int, str]
  4. __main__.X[[int, str]]
  5. >>> X[[int, str]]
  6. __main__.X[[int, str]]

请注意:在某些情况下,具有 ParamSpec 的泛型在替换后可能不具有正确的 __parameters__,因为参数规格主要用于静态类型检查。

在 3.10 版本发生变更: Generic 现在可以通过参数表达式进行参数化。参见 ParamSpecPEP 612 [https://peps.python.org/pep-0612/] 以了解更多细节。

用户定义的泛型类可以将 ABC 作为基类而不会导致元类冲突。 参数化泛型的输出结果会被缓存,且 typing 模块中的大多数类型都是 hashable 并且支持相等性比较。

Any 类型

Any 是一种特殊的类型。静态类型检查器认为所有类型均与 Any 兼容,同样,Any 也与所有类型兼容。

也就是说,可对 Any 类型的值执行任何操作或方法调用,并赋值给任意变量:

  1. from typing import Any
  2.  
  3. a: Any = None
  4. a = [] # 可以
  5. a = 2 # 可以
  6.  
  7. s: str = ''
  8. s = a # 可以
  9.  
  10. def foo(item: Any) -> int:
  11. # 通过类型检查;'item' 可以为任意类型,
  12. # 并且其类型会具有 'bar' 方法
  13. item.bar()
  14. ...

注意,Any 类型的值赋给更精确的类型时,不执行类型检查。例如,把 a 赋给 s,在运行时,即便 s 已声明为 str 类型,但接收 int 值时,静态类型检查器也不会报错。

此外,未指定返回值与参数类型的函数,都隐式地默认使用 Any

  1. def legacy_parser(text):
  2. ...
  3. return data
  4.  
  5. # 静态类型检查器将认为上面的函数
  6. # 具有与下面的函数相同的签名:
  7. def legacy_parser(text: Any) -> Any:
  8. ...
  9. return data

需要混用动态与静态类型代码时,此操作把 Any 当作 应急出口

Anyobject 的区别。与 Any 相似,所有类型都是 object 的子类型。然而,与 Any 不同,object 不可逆:object 不是 其它类型的子类型。

就是说,值的类型是 object 时,类型检查器几乎会拒绝所有对它的操作,并且,把它赋给更精确的类型变量(或返回值)属于类型错误。例如:

  1. def hash_a(item: object) -> int:
  2. # 不能通过类型检查;对象没有 'magic' 方法。
  3. item.magic()
  4. ...
  5.  
  6. def hash_b(item: Any) -> int:
  7. # 通过类型检查
  8. item.magic()
  9. ...
  10.  
  11. # 通过类型检查,因为整数和字符串都是 object 的子类
  12. hash_a(42)
  13. hash_a("foo")
  14.  
  15. # 通过类型检查,因为 Any 可以兼容所有类型
  16. hash_b(42)
  17. hash_b("foo")

使用 object,说明值能以类型安全的方式转为任何类型。使用 Any,说明值是动态类型。

名义子类型 vs 结构子类型

最初 PEP 484 [https://peps.python.org/pep-0484/] 将 Python 静态类型系统定义为使用 名义子类型。这意味着当且仅当类 AB 的子类时,才满足有类 B 预期时使用类 A

此项要求以前也适用于抽象基类,例如,Iterable 。这种方式的问题在于,定义类时必须显式说明,既不 Pythonic,也不是动态类型式 Python 代码的惯用写法。例如,下列代码就遵从了 PEP 484 [https://peps.python.org/pep-0484/] 的规范:

  1. from collections.abc import Sized, Iterable, Iterator
  2.  
  3. class Bucket(Sized, Iterable[int]):
  4. ...
  5. def __len__(self) -> int: ...
  6. def __iter__(self) -> Iterator[int]: ...

PEP 544 [https://peps.python.org/pep-0544/] 允许用户在类定义时不显式说明基类,从而解决了这一问题,静态类型检查器隐式认为 Bucket 既是 Sized 的子类型,又是 Iterable[int] 的子类型。这就是 结构子类型 (又称为静态鸭子类型):

  1. from collections.abc import Iterator, Iterable
  2.  
  3. class Bucket: # 注意:没有基类
  4. ...
  5. def __len__(self) -> int: ...
  6. def __iter__(self) -> Iterator[int]: ...
  7.  
  8. def collect(items: Iterable[int]) -> int: ...
  9. result = collect(Bucket()) # 通过类型检查

此外,结构子类型的优势在于,通过继承特殊类 Protocol ,用户可以定义新的自定义协议(见下文中的例子)。

模块内容

typing 模块定义了下列类、函数和装饰器。

特殊类型原语

特殊类型

这些类型可用于在注解中表示类型,但不支持下标用法([])。

  • typing.Any
  • 特殊类型,表示没有约束的类型。

    • 所有类型都与 Any 兼容。

    • Any 与所有类型都兼容。

在 3.11 版本发生变更: Any 现在可以用作基类。这有助于避免类型检查器在高度动态或可通过鸭子类型使用的类上报错。

定义:

  1. AnyStr = TypeVar('AnyStr', str, bytes)

AnyStr 用于可接受 strbytes 参数但不允许两者混用的函数。

例如:

  1. def concat(a: AnyStr, b: AnyStr) -> AnyStr:
  2. return a + b
  3.  
  4. concat("foo", "bar") # 可以,输出为 'str' 类型
  5. concat(b"foo", b"bar") # 可以,输出为 'bytes' 类型
  6. concat("foo", b"bar") # 错误,不可混用 str 和 bytes

请注意:尽管名为 AnyStr,但它与 Any 类型毫无关系,也不是指“任何字符串”。而且,AnyStr 更是和 str | bytes 彼此互不相同,各有各的使用场景:

  1. # AnyStr 的无效使用:
  2. # 类型变量在函数签名中仅使用一次,
  3. # 因此无法通过类型检查器“解决”
  4. def greet_bad(cond: bool) -> AnyStr:
  5. return "hi there!" if cond else b"greetings!"
  6.  
  7. # 注释此函数的更好方法:
  8. def greet_proper(cond: bool) -> str | bytes:
  9. return "hi there!" if cond else b"greetings!"

Deprecated since version 3.13, will be removed in version 3.18: 已被弃用而应改用新的 类型形参语法。 使用 class A[T: (str, bytes)]: … 而不是导入 AnyStr。 详情参见 PEP 695 [https://peps.python.org/pep-0695/]。

在 Python 3.16 中,AnyStr 将从 typing.__all__ 中被移除,在运行时当它被访问或从 typing 导入时将发出弃用警告。 在 Python 3.18 中 AnyStr 将从 typing 中被移除。

  • typing.LiteralString
  • 只包括字符串字面值的的特殊类型。

任何字符串字面值或其他 LiteralString 都与 LiteralString 兼容。但 str 类型的对象不与其兼容。组合 LiteralString 类型的对象产生的字符串也被认为是 LiteralString

示例:

  1. def run_query(sql: LiteralString) -> None:
  2. ...
  3.  
  4. def caller(arbitrary_string: str, literal_string: LiteralString) -> None:
  5. run_query("SELECT * FROM students") # 可以
  6. run_query(literal_string) # 可以
  7. run_query("SELECT * FROM " + literal_string) # 可以
  8. run_query(arbitrary_string) # 类型检查器错误
  9. run_query( # 类型检查器错误
  10. f"SELECT * FROM students WHERE name = {arbitrary_string}"
  11. )

LiteralString 对于会因用户可输入任意字符串而导致问题的敏感 API 很有用。例如,上述两处导致类型检查器报错的代码可能容易被 SQL 注入攻击。

请参阅 PEP 675 [https://peps.python.org/pep-0675/] 了解详情。

Added in version 3.11.

它们可被用于指明一个函数绝不会返回,例如 sys.exit():

  1. from typing import Never # 或 NoReturn
  2.  
  3. def stop() -> Never:
  4. raise RuntimeError('no way')

或者用于定义一个绝不应被调用的函数,因为不存在有效的参数,例如 assert_never():

  1. from typing import Never # 或 NoReturn
  2.  
  3. def never_call_me(arg: Never) -> None:
  4. pass
  5.  
  6. def int_or_str(arg: int | str) -> None:
  7. never_call_me(arg) # 类型检查器错误
  8. match arg:
  9. case int():
  10. print("It's an int")
  11. case str():
  12. print("It's a str")
  13. case _:
  14. never_call_me(arg) # OK, arg is of type Never (or NoReturn)

NeverNoReturn 在类型系统中具有相同的含义并且静态类型检查器会以相同的方式对待这两者。

Added in version 3.6.2: 增加了 NoReturn

Added in version 3.11: 增加了 Never

  • typing.Self
  • 特殊类型,表示当前闭包内的类。

例如:

  1. from typing import Self, reveal_type
  2.  
  3. class Foo:
  4. def return_self(self) -> Self:
  5. ...
  6. return self
  7.  
  8. class SubclassOfFoo(Foo): pass
  9.  
  10. reveal_type(Foo().return_self()) # 揭示的类型为 "Foo"
  11. reveal_type(SubclassOfFoo().return_self()) # 揭示的类型为 "SubclassOfFoo"

此注解在语法上等价于以下代码,但形式更为简洁:

  1. from typing import TypeVar
  2.  
  3. Self = TypeVar("Self", bound="Foo")
  4.  
  5. class Foo:
  6. def return_self(self: Self) -> Self:
  7. ...
  8. return self

通常来说,如果某些内容返回 self,如上面的示例所示,您应该使用 Self 作为返回值注解。如果 Foo.return_self 被注解为返回 "Foo",那么类型检查器将推断从 SubclassOfFoo.return_self 返回的对象是 Foo 类型,而不是 SubclassOfFoo

其它常见用例包括:

  • 被用作替代构造器的 classmethod,它将返回 cls 形参的实例。

  • 标注一个返回自身的 __enter__() 方法。

如果不能保证在子类中方法会返回子类的实例(而非父类的实例),则不应使用 Self 作为返回值注解:

  1. class Eggs:
  2. # 在这里 self 是一个不正确的返回注释,
  3. # 因为返回的对象始终是 Eggs 的一个实例,
  4. # 即使在子类中
  5. def returns_eggs(self) -> "Eggs":
  6. return Eggs()

更多细节请参见 PEP 673 [https://peps.python.org/pep-0673/]。

Added in version 3.11.

  • typing.TypeAlias
  • 特殊注解,用于显式声明 类型别名.

例如:

  1. from typing import TypeAlias
  2.  
  3. Factors: TypeAlias = list[int]

在较早的 Python 版本上,TypeAlias 对注解使用前向引用的别名时特别有用,因为类型检查器可能很难将这些别名与正常的变量赋值区分开来:

  1. from typing import Generic, TypeAlias, TypeVar
  2.  
  3. T = TypeVar("T")
  4.  
  5. # "Box" 还不存在,
  6. # 因此我们必须在 Python <3.12 版本中使用引号进行前向引用。
  7. # 使用 ``TypeAlias`` 告诉类型检查器这是一个类型别名声明,
  8. # 而不是对字符串的变量赋值。
  9. BoxOfStrings: TypeAlias = "Box[str]"
  10.  
  11. class Box(Generic[T]):
  12. @classmethod
  13. def make_box_of_strings(cls) -> BoxOfStrings: ...

请参阅 PEP 613 [https://peps.python.org/pep-0613/] 了解详情。

Added in version 3.10.

自 3.12 版本弃用: TypeAlias 被弃用,请使用 type 语句,后者创建 TypeAliasType 的实例,并且天然支持正向引用。请注意,虽然 TypeAliasTypeAliasType 具有相似的用途和名称,但它们是不同的,后者并不是前者的类型。目前还没有移除 TypeAlias 的计划,但鼓励用户迁移到 type 语句。

特殊形式

这些内容在注解中可以视为类型,且都支持下标用法([]),但每个都有唯一的语法。

  • typing.Union
  • 联合类型; Union[X, Y] 等价于 X | Y ,意味着满足 X 或 Y 之一。

要定义一个联合类型,可以使用类似 Union[int, str] 或简写 int | str。建议使用这种简写。细节:

  • 参数必须是某种类型,且至少有一个。

  • 联合类型之联合类型会被展平,例如:

  1. Union[Union[int, str], float] == Union[int, str, float]
  • 单参数之联合类型就是该参数自身,例如:
  1. Union[int] == int # 该构造器确实返回 int
  • 冗余的参数会被跳过,例如:
  1. Union[int, str, int] == Union[int, str] == int | str
  • 比较联合类型,不涉及参数顺序,例如:
  1. Union[int, str] == Union[str, int]
  • 不可创建 Union 的子类或实例。

  • 没有 Union[X][Y] 这种写法。

在 3.7 版本发生变更: 在运行时,不要移除联合类型中的显式子类。

在 3.10 版本发生变更: 联合类型现在可以写成 X | Y。 参见 联合类型表达式

  • typing.Optional
  • Optional[X] 等价于 X | None (或 Union[X, None] ) 。

注意,可选类型与含默认值的可选参数不同。含默认值的可选参数不需要在类型注解上添加 Optional 限定符,因为它仅是可选的。例如:

  1. def foo(arg: int = 0) -> None:
  2. ...

另一方面,显式应用 None 值时,不管该参数是否可选, Optional 都适用。例如:

  1. def foo(arg: Optional[int] = None) -> None:
  2. ...

在 3.10 版本发生变更: 可选参数现在可以写成 X | None。 参见 联合类型表达式

  • typing.Concatenate
  • 特殊形式,用于注解高阶函数。

Concatenate 可用于与 CallableParamSpec 连用来注解高阶可调用对象,该可象可以添加、移除或转换另一个可调用对象的形参。 使用形式为 Concatenate[Arg1Type, Arg2Type, …, ParamSpecVariable]Concatenate 目前仅可用作传给 Callable 的第一个参数。传给 Concatenate 的最后一个形参必须是 ParamSpec 或省略号( )。

例如,为了注释一个装饰器 with_lock,它为被装饰的函数提供了 threading.LockConcatenate 可以用来表示 with_lock 期望一个可调用对象,该对象接收一个 Lock 作为第一个参数,并返回一个具有不同类型签名的可调用对象。 在这种情况下,ParamSpec 表示返回的可调用对象的参数类型取决于被传入的可调用程序的参数类型:

  1. from collections.abc import Callable
  2. from threading import Lock
  3. from typing import Concatenate
  4.  
  5. # 使用此锁来确保在任何时候只有一个线程正在执行某个函数。
  6. my_lock = Lock()
  7.  
  8. def with_lock[**P, R](f: Callable[Concatenate[Lock, P], R]) -> Callable[P, R]: '''A type-safe decorator which provides a lock.'''
  9. def inner(*args: P.args, **kwargs: P.kwargs) -> R:
  10. # Provide the lock as the first argument.
  11. return f(my_lock, *args, **kwargs)
  12. return inner
  13.  
  14. @with_lock
  15. def sum_threadsafe(lock: Lock, numbers: list[float]) -> float: '''Add a list of numbers together in a threadsafe manner.'''
  16. with lock:
  17. return sum(numbers)
  18.  
  19. # 由于装饰器的存在,我们不需要自己传递锁。
  20. sum_threadsafe([1.1, 2.2, 3.3])

Added in version 3.10.

参见

  • typing.Literal
  • 特殊类型注解形式,用于定义“字面值类型”。

Literal 可以用来向类型检查器说明被注解的对象具有与所提供的字面量之一相同的值。

例如:

  1. def validate_simple(data: Any) -> Literal[True]: # 总是返回 True
  2. ...
  3.  
  4. type Mode = Literal['r', 'rb', 'w', 'wb']
  5. def open_helper(file: str, mode: Mode) -> str:
  6. ...
  7.  
  8. open_helper('somepath', 'r') # 通过类型检查
  9. open_helper('otherpath', 'typo') # 类型检查错误

Literal[…] 不能创建子类。在运行时,任意值均可作为 Literal[…] 的类型参数,但类型检查器可以对此加以限制。字面量类型详见 PEP 586 [https://peps.python.org/pep-0586/] 。

Added in version 3.8.

在 3.9.1 版本发生变更: Literal 现在能去除形参的重复。 Literal 对象的相等性比较不再依赖顺序。 现在如果有某个参数不为 hashableLiteral 对象在相等性比较期间将引发 TypeError

  • typing.ClassVar
  • 特殊类型注解构造,用于标注类变量。

PEP 526 [https://peps.python.org/pep-0526/] 所述,打包在 ClassVar 内的变量注解是指,给定属性应当用作类变量,而不应设置在类实例上。用法如下:

  1. class Starship:
  2. stats: ClassVar[dict[str, int]] = {} # 类变量
  3. damage: int = 10 # 实例变量

ClassVar 仅接受类型,也不能使用下标。

ClassVar 本身不是类,不应用于 isinstance()issubclass()ClassVar 不改变 Python 运行时行为,但可以用于第三方类型检查器。例如,类型检查器会认为以下代码有错:

  1. enterprise_d = Starship(3000)
  2. enterprise_d.stats = {} # 错误,在实例上设置类变量
  3. Starship.stats = {} # 这是可以的

Added in version 3.5.3.

在 3.13 版本发生变更: 现在 ClassVar 可以被嵌套在 Final 中,反之亦然。

  • typing.Final
  • 特殊类型注解构造,用于向类型检查器表示最终名称。

不能在任何作用域中重新分配最终名称。类作用域中声明的最终名称不能在子类中重写。

例如:

  1. MAX_SIZE: Final = 9000
  2. MAX_SIZE += 1 # 类型检查器将报告错误
  3.  
  4. class Connection:
  5. TIMEOUT: Final[int] = 10
  6.  
  7. class FastConnector(Connection):
  8. TIMEOUT = 1 # 类型检查器将报告错误

这些属性没有运行时检查。详见 PEP 591 [https://peps.python.org/pep-0591/]。

Added in version 3.8.

在 3.13 版本发生变更: 现在 Final 可以被嵌套在 ClassVar 中,反之亦然。

  • typing.Required
  • 特殊类型注解构造,用于标记 TypedDict 键为必填项。

这主要用于 total=False 的 TypedDict。有关更多详细信息,请参阅 TypedDictPEP 655 [https://peps.python.org/pep-0655/] 。

Added in version 3.11.

  • typing.NotRequired
  • 特殊类型注解构造,用于标记 TypedDict 键为可能不存在的键。

详情参见 TypedDictPEP 655 [https://peps.python.org/pep-0655/]。

Added in version 3.11.

  • typing.ReadOnly
  • 一个特殊的类型标注构造,用于将 TypedDict 的项标记为只读。

例如:

  1. class Movie(TypedDict):
  2. title: ReadOnly[str]
  3. year: int
  4.  
  5. def mutate_movie(m: Movie) -> None:
  6. m["year"] = 1999 # allowed
  7. m["title"] = "The Matrix" # 类型检查错误

这个属性没有运行时检查。

详见 TypedDictPEP 705 [https://peps.python.org/pep-0705/]。

Added in version 3.13.

  • typing.Annotated
  • 特殊类型注解形式,用于向注解添加特定于上下文的元数据。

使用注解 Annotated[T, x] 将元数据 x 添加到给定类型 T 。使用 Annotated 添加的元数据可以被静态分析工具使用,也可以在运行时使用。在运行时使用的情况下,元数据存储在 __metadata__ 属性中。

如果库或工具遇到注解 Annotated[T, x] ,并且没有针对这一元数据的特殊处理逻辑,则应该忽略该元数据,简单地将注解视为 T 。因此, Annotated 对于希望将注解用于 Python 的静态类型注解系统之外的目的的代码很有用。

使用 Annotated[T, x] 作为注解仍然允许对 T 进行静态类型检查,因为类型检查器将简单地忽略元数据 x 。因此,Annotated 不同于 @no_type_check 装饰器,后者虽然也可以用于在类型注解系统范围之外添加注解,但是会完全禁用对函数或类的类型检查。

具体解释元数据的方式由遇到 Annotated 注解的工具或库来负责。遇到 Annotated 类型的工具或库可以扫描元数据的各个元素以确定其是否有意处理(比如使用 isinstance() )。

  • Annotated[, ]
  • 以下示例演示在进行区间范围分析时使用 Annotated 将元数据添加到类型注解的方法:

  1. @dataclass
  2. class ValueRange:
  3. lo: int
  4. hi: int

  5. T1 = Annotated[int, ValueRange(-10, 5)]

  6. T2 = Annotated[T1, ValueRange(-20, 3)]

语法细节:

  • Annotated 的第一个参数必须是有效的类型。

  • 可提供多个元数据的元素( Annotated 支持可变参数):

  1. @dataclass
  2. class ctype:
  3. kind: str

  4. Annotated[int, ValueRange(3, 10), ctype("char")]

由处理注解的工具决定是否允许向一个注解中添加多个元数据元素,以及如何合并这些注解。

  • Annotated 至少要有两个参数( Annotated[int] 是无效的)

  • 元数据元素的顺序会被保留,且影响等价检查:

  1. assert Annotated[int, ValueRange(3, 10), ctype("char")] != Annotated[
  2. int, ctype("char"), ValueRange(3, 10)
  3. ]
  • 嵌套的 Annotated 类型会被展平。元数据元素从最内层的注解开始依次展开:
  1. assert Annotated[Annotated[int, ValueRange(3, 10)], ctype("char")] == Annotated[
  2. int, ValueRange(3, 10), ctype("char")
  3. ]
  • 元数据中的重复元素不会被移除:
  1. assert Annotated[int, ValueRange(3, 10)] != Annotated[
  2. int, ValueRange(3, 10), ValueRange(3, 10)
  3. ]
  • Annotated 可以与嵌套别名和泛型别名一起使用:

  1. @dataclass
  2. class MaxLen:
  3. value: int

  4. type Vec[T] = Annotated[list[tuple[T, T]], MaxLen(10)]

  5. 当在类型注释中使用时,类型检查器会将“V”视为

    Annotated[list[tuple[int, int]], MaxLen(10)]:

    type V = Vec[int]

  1. type Variadic[*Ts] = Annotated[*Ts, Ann1] # 不可用

这等价于:

  1. Annotated[T1, T2, T3, ..., Ann1]

其中 T1T2 等都是 TypeVars 。这种写法无效:应当只有一个类型被传递给 Annotated。

  • 默认情况下, get_type_hints() 会去除注解中的元数据。传入 include_extras=True 可以保留元数据:
  1. >>> from typing import Annotated, get_type_hints
  2. >>> def func(x: Annotated[int, "metadata"]) -> None: pass
  3. ...
  4. >>> get_type_hints(func)
  5. {'x': <class 'int'>, 'return': <class 'NoneType'>}
  6. >>> get_type_hints(func, include_extras=True)
  7. {'x': typing.Annotated[int, 'metadata'], 'return': <class 'NoneType'>}
  • 在运行时,与特定 Annotated 类型相关联的元数据可通过 __metadata__ 属性来获取:
  1. >>> from typing import Annotated
  2. >>> X = Annotated[int, "very", "important", "metadata"]
  3. >>> X
  4. typing.Annotated[int, 'very', 'important', 'metadata']
  5. >>> X.__metadata__
  6. ('very', 'important', 'metadata')
  • 在运行时 ,如果要检索由 Annotated 封装的原始类型,请使用 __origin__ 属性:
  1. >>> from typing import Annotated, get_origin
  2. >>> Password = Annotated[str, "secret"]
  3. >>> Password.__origin__
  4. <class 'str'>

请注意使用 get_origin() 将返回 Annotated 本身:

  1. >>> get_origin(Password)
  2. typing.Annotated

参见

Added in version 3.9.

  • typing.TypeIs
  • 特殊类型注解构造,用于标记用户定义的谓词函数。

TypeIs 能用来注解用户定义的谓词函数的返回值类型。TypeIs 接受单个类型参数。如此标注的函数在运行时应当有至少一个位置参数,并且返回一个布尔值。

TypeIs 旨在方便 类型收窄 — 一个被静态类型检查器使用,用来更精准地决定程序代码流中表达式类型的技巧。通常类型收窄通过分析有条件的代码流并对代码块执行类型收窄实现。此处的条件表达式有时也被称为“类型谓词”。

  1. def is_str(val: str | float):
  2. # "isinstance" 类型谓词
  3. if isinstance(val, str):
  4. # ``val`` 的类型缩小为 ``str``
  5. ...
  6. else:
  7. # 否则, ``val`` 的类型缩小为 ``float``。
  8. ...

使用一个用户定义的布尔函数作为类型谓词有时很方便。这样的函数应当将 TypeIs[…]TypeGuard 作为它的返回类型,以向静态类型检查器传达这个意图。TypeIs 的行为通常比 TypeGuard 更直观,但在函数的输入与输出类型不兼容 (例如从 list[object]list[int]) 或函数不会对所有收窄后类型的实例返回 True 时不能使用。

使用 -> TypeIs[NarrowedType] 告诉静态类型检查器对于给定的函数:

  • 返回一个布尔值。

  • 如果返回值是 True,那么其参数的类型收窄到参数本身类型与 NarrowedType 的并。

  • 如果返回值是 False,那么其参数的类型收窄到排除 NarrowedType

例如:

  1. from typing import assert_type, final, TypeIs
  2.  
  3. class Parent: pass
  4. class Child(Parent): pass
  5. @final
  6. class Unrelated: pass
  7.  
  8. def is_parent(val: object) -> TypeIs[Parent]:
  9. return isinstance(val, Parent)
  10.  
  11. def run(arg: Child | Unrelated):
  12. if is_parent(arg):
  13. # ``arg`` 的类型缩小为 ``Parent`` 和 ```Child`` 的交集,
  14. # 相当于 ``Child``.
  15. assert_type(arg, Child)
  16. else:
  17. # ``arg`` 的类型缩小为 ``Parent`` 除外,所以仅剩 ``Unrelated``。
  18. assert_type(arg, Unrelated)

TypeIs 内的类型必须与函数参数类型契合,否则静态类型检查器会引发错误。编写不正确的 TypeIs 可能导致类型系统中出现不健全行为,以类型安全的方式编写这些函数是用户的责任。

如果 TypeIs 函数是一个类或实例方法,那么 TypeIs 中的类型将映射到(在 clsself 之后)第二个形参的类型。

简单来说,def foo(arg: TypeA) -> TypeIs[TypeB]: … 意味着如果 foo(arg) 返回 True,那么 arg 就是 TypeB 的实例,如果返回 False,它就不是 TypeB.的实例。

TypeIs 同样可作用于类型变量,详见 PEP 742 [https://peps.python.org/pep-0742/] (使用 TypeIs 收窄类型) 。

Added in version 3.13.

  • typing.TypeGuard
  • 特殊类型注解构造,用于标记用户定义的谓词函数。

类型谓词函数是由用户定义的函数,它的返回值指示参数是否为某个特定类型的实例。TypeGuardTypeIs 用法相近,但是对类型检查行为有不同的影响(如下)。

-> TypeGuard 告诉静态类型检查器,某函数:

  • 返回一个布尔值。

  • 如果返回值是 True,那么其参数的类型是 TypeGuard 内的类型。

TypeGuard 也适用于类型变量。 详情参见 PEP 647 [https://peps.python.org/pep-0647/]。

例如:

  1. def is_str_list(val: list[object]) -> TypeGuard[list[str]]: '''Determines whether all objects in the list are strings'''
  2. return all(isinstance(x, str) for x in val)
  3.  
  4. def func1(val: list[object]):
  5. if is_str_list(val):
  6. # ``val`` 的类型缩小为 ``list[str]``.
  7. print(" ".join(val))
  8. else:
  9. # ``val`` 的类型仍为 ``list[object]``.
  10. print("Not a list of strings!")

TypeIsTypeGuard 有以下不同:

  • TypeIs 要求收窄的类型是输入类型的子类型,但 TypeGuard 不要求。这主要是为了允许将 list[object] 缩小为 list[str],即使后者不是前者的子类型,因为 list 是不变的。

  • TypeGuard 函数返回 True 时,类型检查器会将变量的类型精确地收窄到 TypeGuard 类型。当 TypeIs 函数返回 True 时,类型检查程序可以结合先前已知的变量类型和 TypeIs 类型推断出更精确的类型。(从技术上讲,这叫做交类型。)

  • TypeGuard 函数返回 False 时,类型检查器不会收窄变量的类型范围。当 TypeIs 函数返回 False 时,类型检查器可以收窄变量的类型范围至排除 TypeIs 类型。

Added in version 3.10.

  • typing.Unpack
  • 在概念上将对象标记为已解包的类型运算符。

例如,在一个 类型变量元组 上使用解包运算符 * 就等价于使用 Unpack 来将该类型变量元组标记为已被解包:

  1. Ts = TypeVarTuple('Ts')
  2. tup: tuple[*Ts]
  3. # 实际所做的:
  4. tup: tuple[Unpack[Ts]]

实际上,Unpacktyping.TypeVarTuplebuiltins.tuple 类型的上下文中可以和 * 互换使用。 你可能会看到 Unpack 在较旧版本的 Python 中被显式地使用,这时 * 在特定场合则是无法使用的:

  1. # 在旧版本的 Python 中,TypeVarTuple 和 Unpack 位于
  2. # `typing_extensions` 反向移植包中。
  3. from typing_extensions import TypeVarTuple, Unpack
  4.  
  5. Ts = TypeVarTuple('Ts')
  6. tup: tuple[*Ts] # Python <= 3.10 时的语法错误!
  7. tup: tuple[Unpack[Ts]] # 语义等效且向后兼容

Unpack 也可以与 typing.TypedDict 一起使用以便在函数签名中对 **kwargs 进行类型标注:

  1. from typing import TypedDict, Unpack
  2.  
  3. class Movie(TypedDict):
  4. name: str
  5. year: int
  6.  
  7. # 此函数需要两个关键字参数 -
  8. # 类型为 `str` 的 `name` 和类型为 `int` 的 `year`。
  9. def foo(**kwargs: Unpack[Movie]): ...

请参阅 PEP 692 [https://peps.python.org/pep-0692/] 了解将 Unpack 用于 **kwargs 类型标注的更多细节。

Added in version 3.11.