python:类型注解的类型 --- Generic Alias 、 Union

python:类型注解的类型 --- Generic Alias 、 Union


type annotations 的内置类型为 Generic Alias 和 Union。

GenericAlias 类型

GenericAlias 对象通常是通过 抽取 一个类来创建的。 它们最常被用于 容器类,如 list 或 dict。 举例来说,list[int] 这个 GenericAlias 对象是通过附带 int 参数抽取 list 类来创建的。 GenericAlias 对象的主要目的是用于 类型标注。

备注 通常一个类只有在实现了特殊方法 class_getitem() 时才支持抽取操作。
GenericAlias 对象可作为 generic type 的代理,实现了 形参化泛型。

对于一个容器类,提供给类的 抽取 操作的参数可以指明对象所包含的元素类型。 例如,set[bytes] 可在类型标注中用来表示一个 set 中的所有元素均为 bytes 类型。

对于一个定义了 class_getitem() 但不属于容器的类,提供给类的抽取操作的参数往往会指明在对象上定义的一个或多个方法的返回值类型。 例如,正则表达式 可以被用在 str 数据类型和 bytes 数据类型上:

如果 x = re.search(‘foo’, ‘foo’),则 x 将为一个 re.Match 对象而 x.group(0) 和 x[0] 的返回值将均为 str 类型。 我们可以在类型标注中使用 GenericAlias re.Match[str] 来代表这种对象。

如果 y = re.search(b’bar’, b’bar’),(注意 b 表示 bytes),则 y 也将为一个 re.Match 的实例,但 y.group(0) 和 y[0] 的返回值将均为 bytes 类型。 在类型标注中,我们将使用 re.Match[bytes] 来代表这种形式的 re.Match 对象。

GenericAlias 对象是 types.GenericAlias 类的实例,该类也可被用来直接创建 GenericAlias 对象。

T[X, Y, …]
创建一个代表由类型 X, Y 来参数化的类型 T 的 GenericAlias,此类型会更依赖于所使用的 T。 例如,一个接受包含 float 元素的 list 的函数:

def average(values: list[float]) -> float:
return sum(values) / len(values)
另一个例子是关于 mapping 对象的,用到了 dict,泛型的两个类型参数分别代表了键类型和值类型。本例中的函数需要一个 dict,其键的类型为 str,值的类型为 int:。

def send_post_request(url: str, body: dict[str, int]) -> None:

内置函数 isinstance() 和 issubclass() 不接受第二个参数为GenericAlias 类型:

>>>
isinstance([1, 2], list[str])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: isinstance() argument 2 cannot be a parameterized generic

Python 运行时不会强制执行 类型标注。 这种行为扩展到了泛型及其类型形参。 当由 GenericAlias 创建容器对象时,并不会检查容器中为元素指定的类型。 例如,以下代码虽然不被鼓励,但运行时并不会报错:

>>>
t = list[str]
t([1, 2, 3])
[1, 2, 3]

不仅如此,在创建对象的过程中,应用了参数后的泛型还会抹除类型参数:

>>>
t = list[str]
type(t)
<class 'types.GenericAlias'>

l = t()
type(l)
<class 'list'>

在泛型上调用 repr() 或 str() 会显示应用参数之后的类型:

>>>
repr(list[int])
'list[int]'

str(list[int])
'list[int]'

调用泛型容器的 getitem() 方法将引发异常以防出现 dict[str][str] 之类的错误:

>>>
dict[str][str]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: There are no type variables left in dict[str]

不过,当使用了 类型变量 时这种表达式是无效的。 索引必须有与 GenericAlias 对象的 args 中的类型变量条目数量相当的元素。

>>>
from typing import TypeVar
Y = TypeVar('Y')
dict[str, Y][int]
dict[str, int]

标准泛型类

下列标准库类支持形参化的泛型。 此列表并不是详尽无遗的。

tuple

list

dict

set

frozenset

type

collections.deque

collections.defaultdict

collections.OrderedDict

collections.Counter

collections.ChainMap

collections.abc.Awaitable

collections.abc.Coroutine

collections.abc.AsyncIterable

collections.abc.AsyncIterable

collections.abc.AsyncGenerator

collections.abc.Iterable

collections.abc.Iterator

collections.abc.Generator

collections.abc.Reversible

collections.abc.Container

collections.abc.Collection

collections.abc.Callable

collections.abc.Set

collections.abc.MutableSet

collections.abc.Mapping

collections.abc.MutableMapping

collections.abc.Sequence

collections.abc.MutableSequence

collections.abc.ByteString

collections.abc.MappingView

collections.abc.KeysView

collections.abc.ItemsView

collections.abc.ValuesView

contextlib.AbstractContextManager

contextlib.AbstractAsyncContextManager

dataclasses.Field

functools.cached_property

functools.partialmethod

os.PathLike

queue.LifoQueue

queue.Queue

queue.PriorityQueue

queue.SimpleQueue

re.Pattern

re.Match

shelve.BsdDbShelf

shelve.DbfilenameShelf

shelve.Shelf

types.MappingProxyType

weakref.WeakKeyDictionary

weakref.WeakMethod

weakref.WeakSet

weakref.WeakValueDictionary

GenericAlias 对象的特殊属性
应用参数后的泛型都实现了一些特殊的只读属性:

genericalias.origin
本属性指向未应用参数之前的泛型类:

>>>
list[int].__origin__
<class 'list'>
genericalias.__args__

该属性是传给泛型类的原始 class_getitem() 的泛型所组成的 tuple (长度可能为 1):

>>>
dict[str, list[int]].__args__
(<class 'str'>, list[int])
genericalias.__parameters__

该属性是延迟计算出来的一个元组(可能为空),包含了 args 中的类型变量。

>>>
from typing import TypeVar

T = TypeVar('T')
list[T].__parameters__
(~T,)

备注 带有参数 typing.ParamSpec 的 GenericAlias 对象,在类型替换后其 parameters 可能会不准确,因为 typing.ParamSpec 主要用于静态类型检查。
genericalias.unpacked

3.11 新版功能.

参见
PEP 484 —— 类型注解
介绍 Python 中用于类型标注的框架。

PEP 585 - 标准多项集中的类型提示泛型
介绍了对标准库类进行原生形参化的能力,只要它们实现了特殊的类方法 class_getitem()。

泛型(Generic), 用户自定义泛型 和 typing.Generic
有关如何实现可在运行时被形参化并能被静态类型检查器所识别的泛用类的文档。

3.9 新版功能.

union 类型

联合对象包含了在多个 类型对象 上执行 | (按位或) 运算后的值。 这些类型主要用于 类型标注。与 typing.Union 相比,联合类型表达式可以实现更简洁的类型提示语法。

X | Y | …
定义包含了 X、Y 等类型的 union 对象。X | Y 表示 X 或 Y。相当于typing.Union[X, Y] 。比如以下函数的参数应为类型 int 或 float :

def square(number: int | float) -> int | float:
return number ** 2
union_object == other
union 对象可与其他 union 对象进行比较。详细结果如下:

多次组合的结果会平推:

(int | str) | float == int | str | float
冗余的类型会被删除:

int | str | int == int | str
在相互比较时,会忽略顺序:

int | str == str | int
与 typing.union 兼容:

int | str == typing.Union[int, str]
Optional 类型可表示为与 None 的组合。

str | None == typing.Optional[str]
isinstance(obj, union_object)
issubclass(obj, union_object)
isinstance() 和 issubclass() 也支持 union 对象:

>>>
isinstance("", int | str)
True

但不能使用包含 parameterized generics 的 union 对象:

isinstance(1, int | list[int])
Traceback (most recent call last):
File “”, line 1, in
TypeError: isinstance() argument 2 cannot contain a parameterized generic
union 对象构成的用户类型可以经由 types.UnionType 访问,并可用于 isinstance() 检查。 而不能由类型直接实例化为对象:

>>>
import types
isinstance(int | str, types.UnionType)
True
types.UnionType()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot create 'types.UnionType' instances

备注 为了支持 X | Y 语法,类型对象加入了 or() 方法。若是元类已实现了 or(),union 也可以覆盖掉:

>>>
class M(type):
    def __or__(self, other):
        return "Hello"

class C(metaclass=M):
    pass

C | int
'Hello'
int | C
int | __main__.C

参见 PEP 604 —— 提出了 X | Y 语法和 union 类型。
3.10 新版功能.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个天秤座的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值