Python类型形参列表

在Python中,类型形参列表是指在函数定义中使用泛型类型的占位符。这些占位符可以用于指定函数接受的参数类型、返回值类型以及函数内部变量的类型等。类型参数是紧接在函数、类或类型别名的名称之后的方括号 ([]) 中声明的。

一、类型形参列表

type_params ::= "[" type_param ("," type_param)* "]"
type_param ::= typevar | typevartuple | paramspec
typevar ::= identifier (":" expression)?
typevartuple ::= "*" identifier
paramspec ::= "**" identifier

函数 (包括 协程), 类 和 类型别名 可能包含类型形参列表:

def max[T](args: list[T]) -> T:
...
async def amax[T](args: list[T]) -> T:
...
class Bag[T]:
def __iter__(self) -> Iterator[T]:
...
def add(self, arg: T) -> None:
...
type ListOrSet[T] = list[T] | set[T]

从语义上讲,这表明函数、类或类型别名是类型变量的泛型,主要供静态类型检查器使用,并且在运行时,泛型对象的行为与其对应的非泛型对象非常相似。

类型参数是紧接在函数、类或类型别名的名称之后的方括号 ([]) 中声明的,类型参数可在泛型对象的作用域内访问,但不能在其他地方访问,因此,在声明 def func[T](): pass 之后,模块作用域中就不能再使用 T 这个名称。 类型形参的作用域是用一个特殊函数 (从技术上说,是一个 标注作用域) 来模拟的,它封装了泛型对象的创建操作。

泛型函数、类和类型别名都有一个 __type_params__ 属性用于列出它们的类型形参。类型形参可分为三种:

1、typing.TypeVar,由一个普通名称 (例如 T) 引入。 从语义上讲,这对类型检查器来说代表了一个单独类型。

2、typing.TypeVarTuple,通过在前面添加一个星号的名称来引入 (例如 *Ts)。 从语义上讲,它代表由任意多个类型组成的元组。

3、typing.ParamSpec,通过在前面添加两个星号的名称来引入 (例如 **P)。 从语义上讲,它代表一个可调用对象的形参。

typing.TypeVar 声明可以通过在冒号 (: ) 后跟一个表达式来定义范围和约束。 冒号后的单独表达式表示一个范围 (例如 T: int), typing.TypeVar 能表示的类型只能是该范围的子类型,冒号后在圆括号内的表达式元组指定了一组约束 (例如 T: (str, bytes))。 元组中的每个成员都应为一个类型 (同样,在运行时并不强制要求这一点)。 约束的类型变量只能使用约束列表内的类型中选择一种。

对于使用类型形参列表语法声明的 typing.TypeVar,范围和约束在创建泛型对象时并不会被求值,只有在通过属性 __bound__ 和 __constraints__ 显式地访问它时才会被求值。 要做到这一点,需要在单独的标注作用域中对范围和约束进行求值。

注意:typing.TypeVarTuple 和 typing.ParamSpec 不能拥有范围或约束。

下面的例子显示了所有被允许的类型形参声明:

def overly_generic[
SimpleTypeVar,
TypeVarWithBound: int,
TypeVarWithConstraints: (str, bytes),
*SimpleTypeVarTuple,
**SimpleParamSpec,
](
a: SimpleTypeVar,
b: TypeVarWithBound,
c: Callable[SimpleParamSpec, TypeVarWithConstraints],
*d: SimpleTypeVarTuple,
): ...

二、泛型函数

泛型函数的声明方式如下:

def func[T](arg: T): ...

该语法等价于:

annotation-def TYPE_PARAMS_OF_func():
T = typing.TypeVar("T")
def func(arg: T): ...
func.__type_params__ = (T,)
return func
func = TYPE_PARAMS_OF_func()

这里 annotation-def 指定了一个 标注作用域,它在运行时并不会实际绑定到任何名称。 (另一项自由是在翻译中达成的:该语法没有通过 typing 模块的属性访问,而是直接创建了一个 typing.TypeVar 的实例)。

泛型函数的标注会在用于声明类型形参的标注作用域内进行求值,但函数的默认值和装饰器则不会。下面的例子演示了针对这些场景,以及类型形参的变化形式的作用域规则:

@decorator
def func[T: int, *Ts, **P](*args: *Ts, arg: Callable[P, T] = some_default):
...

除了 TypeVar 绑定的惰性求值以外,这等同于:

DEFAULT_OF_arg = some_default
annotation-def TYPE_PARAMS_OF_func():
annotation-def BOUND_OF_T():
return int
# In reality, BOUND_OF_T() is evaluated only on demand.
T = typing.TypeVar("T", bound=BOUND_OF_T())
Ts = typing.TypeVarTuple("Ts")
P = typing.ParamSpec("P")
def func(*args: *Ts, arg: Callable[P, T] = DEFAULT_OF_arg):
...
func.__type_params__ = (T, Ts, P)
return func
func = decorator(TYPE_PARAMS_OF_func())

大写形式的名称如 DEFAULT_OF_arg 在运行时不会被实际绑定。

三、泛型类

泛型类的声明方式如下:

class Bag[T]: ...

该语法等价于:

annotation-def TYPE_PARAMS_OF_Bag():
T = typing.TypeVar("T")
class Bag(typing.Generic[T]):
__type_params__ = (T,)
...
return Bag
Bag = TYPE_PARAMS_OF_Bag()

这里还是用 annotation-def (不是真正的关键字) 指明 标注作用域,而名称TYPE_PARAMS_OF_Bag 在不会运行时实际被绑定。

泛型类隐式地继承自 typing.Generic。 泛型类的基类和关键字参数在类型形参的类型作用域内进行求值,而装饰器则在该作用域之外进行求值。 以下示例对此进行了说明:

@decorator
class Bag(Base[T], arg=T): ...

这相当于:

annotation-def TYPE_PARAMS_OF_Bag():
T = typing.TypeVar("T")
class Bag(Base[T], typing.Generic[T], arg=T):
__type_params__ = (T,)
...
return Bag
Bag = decorator(TYPE_PARAMS_OF_Bag())

四、泛型类型别名

type 语句也可被用来创建泛型类型别名:

type ListOrSet[T] = list[T] | set[T]

除了会对值执行 惰性求值 以外,这等同于:

annotation-def TYPE_PARAMS_OF_ListOrSet():
T = typing.TypeVar("T")
annotation-def VALUE_OF_ListOrSet():
return list[T] | set[T]
# In reality, the value is lazily evaluated
return typing.TypeAliasType("ListOrSet", VALUE_OF_ListOrSet(), type_params=(T,))
ListOrSet = TYPE_PARAMS_OF_ListOrSet()
  • 这里annotation-def (不是一个真正的关键字) 指明标注作用域;
  • 像TYPE_PARAMS_OF_ListOrSet 这样的大写名称不会在运行时实际被绑定。
广告合作
QQ群号:707632017

温馨提示:

1、本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。邮箱:2942802716#qq.com。(#改为@)

2、本站原创内容未经允许不得转裁,转载请注明出处“站长百科”和原文地址。

目录