提问人:joel 提问时间:1/27/2020 最后编辑:joel 更新时间:10/15/2023 访问量:37968
TypeVar('T', A, B) 和 TypeVar('T', bound=Union[A, B]) 之间的区别
Difference between TypeVar('T', A, B) and TypeVar('T', bound=Union[A, B])
问:
以下两个有什么区别?TypeVar
from typing import TypeVar, Union
class A: pass
class B: pass
T = TypeVar("T", A, B)
T = TypeVar("T", bound=Union[A, B])
我相信在 Python 3.12 中,这就是这两个边界之间的区别
class Foo[T: (A, B)]: ...
class Foo[T: 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”
相关:这个问题关于和 的区别。Union[A, B]
TypeVar("T", A, B)
答:
当你这样做时,你说 T 可以绑定到 的任一或任何子类型。它是工会的上限。T = TypeVar("T", bound=Union[A, B])
Union[A, B]
Union[A, B]
因此,例如,如果您有一个 type 的函数,则传入以下任何类型的值都是合法的:def f(x: T) -> T
Union[A, B]
(或 A 和 B 的任何亚型的结合,例如Union[A, BChild]
)A
(或 A 的任何亚型)B
(或 B 的任何亚型)
这就是泛型在大多数编程语言中的行为方式:它们允许你施加一个单一的上限。
但是当你这样做时,你基本上是在说必须要么是 A 的上限,要么是 B 的上限。也就是说,您不是建立单个上限,而是建立多个上限!T = TypeVar("T", A, B)
T
因此,这意味着虽然传入任何一种类型的值或 into 都是合法的,但传入是不合法的,因为联合既不是 A 也不是 B 的上限。A
B
f
Union[A, B]
例如,假设你有一个可以包含 ints 或 strs 的可迭代对象。
如果你希望这个迭代对象包含任意 ints 或 strs 的混合,你只需要一个 .例如: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)
相反,如果要强制函数接受所有 int 或所有 str 的列表,但绝不接受两者的混合,则需要多个上限。
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)
评论
TypeVar("T", A, B)
经过一堆阅读,我相信 mypy 正确地提出了 OP 问题中的错误:type-var
generics.py:31:错误:“X”的类型变量“T”的值不能是“AA”
请参阅以下说明。
第二种情况:TypeVar("T", bound=Union[A, B])
我认为@Michael0x2a的回答很好地描述了正在发生的事情。
第一种情况:TypeVar("T", A, B)
原因归结为 Liskov 替代原理 (LSP),也称为行为子类型。解释这超出了本答案的范围,您需要阅读 + 理解 vs 的含义。invariance
covariance
默认情况下,类型变量是不变的。
根据这些信息,意味着类型变量具有类和 的值限制,但因为它是不变的......它只接受这两个(而不是 OR 的任何子类)。T = TypeVar("T", A, B)
T
A
B
A
B
因此,当传递时,mypy 会正确地引发错误。AA
type-var
然后你可能会说:好吧,不能正确匹配 的行为子类型 ?在我看来,你是对的。AA
A
为什么?因为可以正确地将 out 和 替换为 ,并且程序的行为将保持不变。A
AA
但是,由于 mypy 是一个静态类型检查器,mypy 无法弄清楚这一点(它无法检查运行时行为)。必须通过语法显式声明协方差。covariant=True
另请注意:指定协变时,应使用类型变量名称中的后缀。这记录在 PEP 484 中。TypeVar
_co
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] 与 List[superclass] 不兼容
- python/mypy #7049: [问题] 为什么实例方法参数中不允许使用协变类型变量?
- 包含@Michael0x2a的一个很好的例子
评论
T = TypeVar("T", A, B)
A
T
mypy --strict
Also note that if the arguments are instances of some subclass of str, the return type is still plain str.
T_co = TypeVar("T_co", AA, A)
同样通过mypy检查。使用 mypy 0.971 进行测试(在 python 3.9 和 3.10 上)
评论
MyUnion
Union[int, str]
int
Union[A, B]
T1 = TypeVar('T1')
T2 = TypeVar('T2', bound=T2)
T3 = TypeVar('T3', T2, int)
T = TypeVar("T", Union[A, B])