如何在没有 "isinstance" 的情况下将联合类型的值传递给泛型函数?

How to pass Union-typed values to generic function without "isinstance"?

我对如何在集合中存储 generics/unions 感到困惑。考虑下面的 T 可以是 boolstr。我们可以将其表示为边界 TypeVarUnion。当在语义上要求泛型是通过多个命名的单一类型时,差异很重要:

from typing import TypeVar, Union, List, NoReturn

T = TypeVar("T", bool, str)

T_Union = Union[bool, str]

def generic_function(event: T) -> T:
    # must return same type as passed in
    ...

def union_function(event: T_Union) -> T_Union:
    # can return any type in T_Union, not necessarily what was passed in
    ...

现在,如果我想在列表中存储一些可以传递给这些函数的有效值,我发现列表类型不能包含未绑定的泛型,所以我将这些值存储在 Union其中每个元素都是 T:

类型之一
unbound_list_of_event_types: List[T] = [True, "foo"]  # error: Type variable "__main__.T" is unbound
list_of_event_types: List[T_Union] = [True, "foo"]

但是,我无法使用 Union 类型的值调用泛型函数:

generic_function(list_of_event_types[0])  # error: Value of type variable "T" of "generic_function" cannot be "Union[bool, str]"
union_function(list_of_event_types[0])

这让我觉得很奇怪,因为如果我们详尽地检查 Union 中的类型,每个函数调用都采用相同的形式,这看起来像是不必要的 isinstance-checking:

def assert_never(value: NoReturn) -> NoReturn: ...

# Exhaustively checking the types and calling the function with the *same signature*
if isinstance(list_of_event_types[0], bool):
    generic_function(list_of_event_types[0])
elif isinstance(list_of_event_types[0], str):
    generic_function(list_of_event_types[0])
else:
    assert_never(list_of_event_types[0])

# Seems redundant when we could do this:
generic_function(list_of_event_types[0])  # type: ignore[type-var]

https://mypy-play.net/?mypy=0.931&python=3.10&gist=6133504b68fa1e74e844d0fc280ee42f

是否有更好的方法将这些值存储在一个集合中,以便将它们传递给通用函数?


使用 Union 允许输入检查:

T2 = TypeVar("T2", bound=Union[bool, str])

def generic_union_bound_function(event: T2) -> T2:
    # must return same type as passed in
    ...

list_of_event_types: List[Union[bool, str]] = [True, "foo"]

reveal_type(generic_union_bound_function(list_of_event_types[0]))  # bool | str
reveal_type(generic_union_bound_function(True))  # bool

但是如果我们想用Type[T]推导泛型呢? (这个例子更像是各种事件类型的回调注册)。我为这种情况打开了一个 mypy 错误:https://github.com/python/mypy/issues/12115

这里的问题是 list[Union[t1,t2]] 意味着类型是 t1t2t1t2t1t2 对于使用 TypeVar('T', t1, t2) 键入的内容不是有效类型,因为这意味着 't1' 或 't2' 但不是两者。在你的 boolstr 的例子中,可能没有任何东西既是 bool 又是 str,但一般来说,你可以有类似

的东西
class X:
    pass

class Y:
   pass

class XY(X,Y):
   pass

T_U = Union[X,Y]

x : T_U = X()
y : T_U = Y()
xy : T_U = XY()

现在,您可以在示例中使用的是绑定到 bool 和 str 联合的 TypeVar。如果你用它键入一个函数,它仍然会解析为输入类型等于输出类型。它还允许潜在类型是联合中两种类型的类型。然而,无论如何,这对于 bool 和 str 来说是不可能的。所以如果你尝试那个代码

T = TypeVar("T", bound= Union[bool, str])
def some_function(e:TR) -> TR:
    ...
    
a = some_function"a")
b = some_function(1)

d = a.split() # works
c = b.split() # works error: "int" has no attribute "split"

它会准确检查您想要的内容。