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

Difference between TypeVar('T', A, B) and TypeVar('T', bound=Union[A, B])

提问人:joel 提问时间:1/27/2020 最后编辑:joel 更新时间:10/15/2023 访问量:37968

问:

以下两个有什么区别?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)

泛型类型 提示 python 类型

评论

0赞 Michael0x2a 1/28/2020
@Carcigenicate -- 关于您的第一条注释,类型检查器总是执行子类型检查,无论您使用的是哪种类型的 TypeVar,或者您是否使用泛型。这实际上是几乎所有具有名义子类型的类型系统都会做的事情 - 例如,参见Java和C++。您的示例不起作用的原因是,虽然可能是 的子类型,但它不是 的子类型。MyUnionUnion[int, str]int
0赞 Michael0x2a 1/28/2020
关于您的第三条评论,是符合 PEP 484 的有效边界,因为该类型不包含任何类型变量——类型变量是使用 TypeVar 创建的类型。因此,例如,如果您这样做了,那么尝试在另一个 TypeVar 定义中使用 T1 是非法的,方法是执行 或 。这种限制主要存在,因此类型检查器不需要实现高阶类型,这是一个非常复杂的类型系统功能。Union[A, B]T1 = TypeVar('T1')T2 = TypeVar('T2', bound=T2)T3 = TypeVar('T3', T2, int)
0赞 pabouk - Ukraine stay strong 7/29/2023
还有第三种选择:T = TypeVar("T", Union[A, B])
1赞 joel 7/29/2023
@pabouk-Ukrainestaystrong mypy 不允许单个约束

答:

98赞 Michael0x2a 1/28/2020 #1

当你这样做时,你说 T 可以绑定到 的任一或任何子类型。它是工会的上限T = TypeVar("T", bound=Union[A, B])Union[A, B]Union[A, B]

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

  1. Union[A, B](或 A 和 B 的任何亚型的结合,例如Union[A, BChild])
  2. A(或 A 的任何亚型)
  3. B(或 B 的任何亚型)

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


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

因此,这意味着虽然传入任何一种类型的值或 into 都是合法的,但传入是合法的,因为联合既不是 A 也不是 B 的上限。ABfUnion[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)

评论

0赞 joel 1/28/2020
谢谢你的回答。还有一些我不明白的地方。我为问题添加了一个示例,我无法从您提供的信息中解决该问题
0赞 Michael0x2a 1/28/2020
@JoelB -- 我无法重现您在新示例中遇到的错误,至少在使用 mypy 0.761 时是这样。如果您将示例更新为更完整的重现,我很乐意再看一眼。
0赞 joel 1/28/2020
我在 python 3.6 中使用相同的 mypy 版本。您使用的是哪个 Python 版本?
0赞 Michael0x2a 1/28/2020
@JoelB -- 我正在使用 Python 3.7,但切换到 Python 3.6 似乎没有区别 -- 例如,参见 mypy-play.net/...
1赞 joel 1/28/2020
是的,我在 3.6 和 3.7 上看到了错误。该错误仅适用于TypeVar("T", A, B)
6赞 Intrastellar Explorer 5/24/2020 #2

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

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

请参阅以下说明。


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

我认为@Michael0x2a的回答很好地描述了正在发生的事情。


第一种情况:TypeVar("T", A, B)

原因归结为 Liskov 替代原理 (LSP),也称为行为子类型。解释这超出了本答案的范围,您需要阅读 + 理解 vs 的含义。invariancecovariance

来自 python 的 TypeVar 类型文档

默认情况下,类型变量是不变的。

根据这些信息,意味着类型变量具有类和 的值限制,但因为它是不变的......它只接受这两个(而不是 OR 的任何子类)。T = TypeVar("T", A, B)TABAB

因此,当传递时,mypy 会正确地引发错误。AAtype-var


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

为什么?因为可以正确地将 out 和 替换为 ,并且程序的行为将保持不变。AAA

但是,由于 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相关问题的进一步阅读:

评论

0赞 xuiqzy 10/20/2020
当我在需要类型的函数中使用并放置子类时,不会显示错误。此外,在 python 文档 ( docs.python.org/3/library/typing.html#typing.TypeVar ) 中,它说似乎子类型对于他们的示例是可以的,这与我正在谈论的相同。有人知道为什么吗?T = TypeVar("T", A, B)ATmypy --strictAlso note that if the arguments are instances of some subclass of str, the return type is still plain str.
0赞 Intrastellar Explorer 10/21/2020
我不确定@xuiqzy,您是否能够以某种方式共享最小的复制(例如:通过 GitHub Gist)?
0赞 ktb 1/11/2022
查看 mypy 源代码显示子类型是允许的。
0赞 bravmi 8/25/2022
T_co = TypeVar("T_co", AA, A)同样通过mypy检查。使用 mypy 0.971 进行测试(在 python 3.9 和 3.10 上)