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 【问题描述】:我很难理解以下两个TypeVar
s 之间的区别:
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的上限。也就是说,而不是建立一个单个上限,你可以建立多个!
所以这意味着虽然将A
或B
类型的值传递给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 ints 或 all 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。解释这一点超出了此答案的范围,您需要阅读 + 了解 invariance
与 covariance
的含义。
来自python's typing
docs for TypeVar
:
默认类型变量是不变的。
基于此信息,T = TypeVar("T", A, B)
表示类型变量 T
具有类 A
和 B
的值限制,但因为它是不变的......它只接受这两个(而不是 @ 的任何子类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])
,因为:
A
和 B
不相关
您希望允许它们的子类
进一步阅读 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]) 的区别的主要内容,如果未能解决你的问题,请参考以下文章