Python中union语法的介绍

144 阅读4分钟

联盟语法

(我想把这个作为一个快速的帖子来做,以回应我收到的关于这个话题的一些问题。我意识到这可能会重新开始关于类型的最佳语法的讨论,但是对不起,伙计们,PEP 484已经在近一年前被接受了,经过了许多月的讨论和数百条信息。你在这里能想到的任何想法都不可能是新的。这篇帖子只是解释了一个特定决定的理由,并试图把它放在一些背景中)。

我听到有人对PEP 484中的union语法有怨言。Union[X, Y, Z] (其中 X, Y 和 Z 是任意的类型表达式)。过去有人建议用X|Y|Z,或者(X, Y, Z)或者{X, Y, Z}。为什么我们选择了公认的更笨重的Union[X, Y, Z]?

首先,尽管所有的注意力都集中在它身上,但联合实际上是一个相当小的功能,你不应该经常使用它们。所以你也不应该关心那么多。

为什么不是X|Y|Z?

这是不可能的,因为我们希望与已经冻结的 Python 3 版本兼容 (见下文)。我们希望能够表达例如int和str的联合,在这个符号下,可以写成int|str。但是为了实现这个目标,我们必须修改内建的 'type' 类来实现 __or__ -- 而这在已经冻结的 Python 版本中是不可行的。只对从打字模块导入的类型 (如 List) 支持 X|Y,而对内建类型支持其他符号,只会造成混乱。所以 X|Y|Z 被淘汰了。

为什么不是{X,Y,Z}?

这是一个具有X、Y和Z元素的集合,使用内建集合的符号。我们可以有效地把类型看作是值的集合,这使得联合也是一个值的集合(这就是为什么它被称为联合 :-)。

然而,{X, Y, Z}混淆了类型 集和值集,我认为这是致命的错误。这只会造成无尽的混乱。

这个符号在取几个重叠的类的并集时也会造成混乱,例如,如果我们有类B和C,其中C继承于B,那么B和C的并集就是B。相比之下,X|Y 符号实际上可以解决这个问题 (因为原则上我们可以重载 __or__ 来做我们想做的事情),而来自 PEP 484 的 Union[] 操作符 ("functor"?) 确实解决了这个问题 -- 在这个例子中 Union[B, C] 返回 (非联合) 类型 B,无论是在类型检查器还是在运行时。

为什么不是(X,Y,Z)?

这就是元组(X, Y, Z)。它和{X, Y, Z}有同样的缺点,但至少它的优点是类似于union作为isinstance()参数的表达方式,例如isinstance(x, (int, str, list))或isinstance(x, (Sequence, Mapping))。(类似地,except子句:try:.../ except (KeyError, IndexError)。...)

图元的另一个问题是,图元语法已经在很多方面被重载了,以至于它将更容易与其他用途混淆。一个特别的混淆是其他通用类型,对于这些类型,我们仍然希望使用方括号。(如果你有一个整数的迭代器,你真的不能击败Iterable[int]的清晰性。)假设你有一串可能是整数或字符串的值。在PEP 484符号中,我们把它写成Sequence[Union[int, str]]。使用元组符号,我们想把它写成Sequence[(int, str)]。但是事实证明,元类上的 __getitem__ 重载不能区分 Sequence[(int, str)] 和 Sequence[int, str] -- 我们想拒绝后者,因为 Sequence[] 是一个关于单个参数的泛型类。(一个关于两个参数的泛型类的例子是Mapping[K, V]。)消除这一切将使我们处于非常脆弱的状态。

这个想法的棺材里的钉子是用(X, Y, Z)来表示一个有三个项目的元组,其类型分别为X、Y和Z,这个想法与之竞争。但这与Foo[(X, Y)] vs. Foo[X, Y]的问题相同。(而且,没有简单的方法来描述PEP 484所说的Tuple[X, ...],即一个具有统一项目类型X的可变长度元组)。

PS.为什么支持旧的 Python 3 版本?

支持旧版本的原因是采用。只有相对较少的早期采用者可以在最新的Python版本出来后立即升级到它;而我们其他人则被困在旧版本上 (甚至是Python 2.7!)。

所以对于 PEP 484 和打字模块,我们希望支持 3.2 及以上版本 -- 我们选择 3.2 是因为它是最新的 Python 3,被一些较老但仍然流行的 Ubuntu 和 Debian 发行版支持。(另外,3.0 和 3.1 在发布时太不成熟,不可能有大量的追随者。)

有一个打字包,你可以用 pip 轻松安装,它定义了打字的各种有用的东西,从 Any 和 Union 到 List 和 Sequence 的通用版本。但是这样的包不能修改现有的内建程序,如 int 或 list。

(最终我们还增加了对 Python 2.7 的支持,使用了函数签名的类型注释。)