TypeVar('T', A, B) 和 TypeVar('T', bound=Union[A, B]) 的区别

Posted

技术标签:

【中文标题】TypeVar(\'T\', A, B) 和 TypeVar(\'T\', bound=Union[A, B]) 的区别【英文标题】:Difference between TypeVar('T', A, B) and TypeVar('T', bound=Union[A, B])TypeVar('T', A, B) 和 TypeVar('T', bound=Union[A, B]) 的区别 【发布时间】:2020-05-12 23:52:12 【问题描述】:

我很难理解以下两个TypeVars 之间的区别:

from typing import TypeVar, Union

class A: pass
class B: pass

T = TypeVar("T", A, B)
T = TypeVar("T", bound=Union[A, B])

有没有人想解惑?


举个我不明白的例子:这通过了类型检查...

T = TypeVar("T", bound=Union[A, B])

class AA(A):
    pass


class X(Generic[T]):
    pass


class XA(X[A]):
    pass


class XAA(X[AA]):
    pass

...但是使用T = TypeVar("T", A, B),它失败了

错误:“X”的类型变量“T”的值不能是“AA”


相关:this questionUnion[A, B]TypeVar("T", A, B) 之间的区别。

【问题讨论】:

@Carcigenicate -- 关于您的第一条评论,类型检查器总是 进行子类型检查,无论您使用的是哪种类型的 TypeVar 或是否使用仿制药。这实际上是几乎所有具有名义子类型的类型系统都会做的事情——例如,参见 Java 和 C++。您的示例不起作用的原因是,虽然 MyUnion 可能是 Union[int, str] 的子类型,但它不是 int 的子类型。 关于您的第三条评论,Union[A, B] 是根据 PEP 484 的有效绑定,因为该类型不包含任何类型变量——类型变量是使用 TypeVar 创建的类型。例如,如果您执行了T1 = TypeVar('T1'),那么通过执行T2 = TypeVar('T2', bound=T2)T3 = TypeVar('T3', T2, int) 在另一个TypeVar 定义中尝试使用T1 将是非法的。这种限制主要存在,因此类型检查器不需要实现高阶类型,这是一个非常复杂的类型系统功能。 【参考方案1】:

当您执行T = TypeVar("T", bound=Union[A, B]) 时,您是说T 可以绑定到Union[A, B]Union[A, B] 的任何子类型。它是联合的上限

例如,如果您有一个def f(x: T) -> T 类型的函数,则传入以下任何类型的值都是合法的:

    Union[A, B](或 A 和 B 的任何子类型的并集,例如 Union[A, BChild]A(或 A 的任何子类型) B(或 B 的任何子类型)

这就是泛型在大多数编程语言中的行为方式:它们允许您强加一个上限。


但是当你做T = TypeVar("T", A, B)时,你基本上是在说T 必须要么是A的上限,要么是B的上限。也就是说,而不是建立一个单个上限,你可以建立多个!

所以这意味着虽然将AB 类型的值传递给f 是合法的,但传递Union[A, B] 是合法的, union 既不是 A 也不是 B 的上限。


例如,假设您有一个可包含整数或字符串的可迭代对象。

如果您希望这个可迭代对象包含任何整数或字符串的任意混合,您只需要一个 Union[int, str] 的上限。例如:

from typing import TypeVar, Union, List, Iterable

mix1: List[Union[int, str]] = [1, "a", 3]
mix2: List[Union[int, str]] = [4, "x", "y"]
all_ints = [1, 2, 3]
all_strs = ["a", "b", "c"]


T1 = TypeVar('T1', bound=Union[int, str])

def concat1(x: Iterable[T1], y: Iterable[T1]) -> List[T1]:
    out: List[T1] = []
    out.extend(x)
    out.extend(y)
    return out

# Type checks
a1 = concat1(mix1, mix2)

# Also type checks (though your type checker may need a hint to deduce
# you really do want a union)
a2: List[Union[int, str]] = concat1(all_ints, all_strs)

# Also type checks
a3 = concat1(all_strs, all_strs)

相反,如果您想强制该函数接受 all intsall strs 的列表,但绝不是两者的混合,则需要多个上限。

T2 = TypeVar('T2', int, str)

def concat2(x: Iterable[T2], y: Iterable[T2]) -> List[T2]:
    out: List[T2] = []
    out.extend(x)
    out.extend(y)
    return out

# Does NOT type check
b1 = concat2(mix1, mix2)

# Also does NOT type check
b2 = concat2(all_ints, all_strs)

# But this type checks
b3 = concat2(all_ints, all_ints)

【讨论】:

感谢您的回答。还有一些我不明白的地方。我已经为问题添加了一个示例,我无法从您提供的信息中解决 @JoelB -- 我无法重现您在新示例中遇到的错误,至少在使用 mypy 0.761 时是这样。如果您将示例更新为更完整的重现,我很高兴再看一遍。 我使用与 python 3.6 相同的 mypy 版本。你用的是什么python版本? @JoelB -- 我正在使用 Python 3.7,但切换到 Python 3.6 似乎没有什么不同 -- 例如,请参阅 mypy-play.net/…。 是的,我看到 3.6 和 3.7 的错误。错误适用于TypeVar("T", A, B)【参考方案2】:

经过大量阅读,我相信 mypy 正确地提出了 OP 问题中的 type-var 错误:

generics.py:31:错误:“X”的类型变量“T”的值不能是“AA”

请看下面的解释。


第二种情况:TypeVar("T", bound=Union[A, B])

我认为@Michael0x2a's answer 很好地描述了正在发生的事情。


第一个案例:TypeVar("T", A, B)

原因归结为Liskov Substitution Principle (LSP),也称为behavioral subtyping。解释这一点超出了此答案的范围,您需要阅读 + 了解 invariancecovariance 的含义。

来自python's typing docs for TypeVar

默认类型变量是不变的。

基于此信息,T = TypeVar("T", A, B) 表示类型变量 T 具有类 AB 的值限制,但因为它是不变的......它只接受这两个(而不是 @ 的任何子类987654339@ 或 B)。

因此,当通过 AA 时,mypy 正确地引发了 type-var 错误。


然后您可能会说:好吧,AA 不正确匹配 A 的行为子类型吗?在我看来,你是对的。

为什么?因为可以正确地将A替换为AA,程序的行为不会改变。

但是,因为 mypy 是一个静态类型检查器,所以 mypy 无法解决这个问题(它无法检查运行时行为)。必须通过语法covariant=True 明确声明协方差。

另请注意:在指定协变 TypeVar 时,应在类型变量名称中使用后缀 _co。这记录在PEP 484 here。

from typing import TypeVar, Generic

class A: pass
class AA(A): pass

T_co = TypeVar("T_co", AA, A, covariant=True)

class X(Generic[T_co]): pass

class XA(X[A]): pass
class XAA(X[AA]): pass

输出:Success: no issues found in 1 source file


那么,你应该怎么做呢?

我会使用TypeVar("T", bound=Union[A, B]),因为:

AB 不相关 您希望允许它们的子类

进一步阅读 mypy 中与 LSP 相关的问题:

python/mypy #2984: List[subclass] is incompatible with List[superclass] python/mypy #7049: [Question] why covariant type variable isn't allowed in instance method parameter? 包含来自@Michael0x2a 的一个很好的例子

【讨论】:

当我使用T = TypeVar("T", A, B) 并将A 的子类放入需要T 类型的函数中时,mypy --strict 不会显示错误。此外,在 python 文档 (docs.python.org/3/library/typing.html#typing.TypeVar) 中,它说 Also note that if the arguments are instances of some subclass of str, the return type is still plain str. 似乎子类型对于他们的示例来说是可以的,这与我正在谈论的那个相同。有谁知道为什么? 我不确定@xuiqzy,你能以某种方式分享一个最小的复制品吗(例如:通过 GitHub Gist)? 看mypy源码可以看出子类型是允许的。

以上是关于TypeVar('T', A, B) 和 TypeVar('T', bound=Union[A, B]) 的区别的主要内容,如果未能解决你的问题,请参考以下文章

Python 类型提示:Callable 后跟 TypeVar 是啥意思?

通用列表联盟没有按我期望的方式工作

实现协议的 Python 泛型类型

如何在 python 中继承类型提示?

python泛型

python泛型