Python,MyPy:如何在泛型的限制中"exclusive OR"类型?



我想使用泛型,这样我就可以避免使用"不变的Xyz"问题,并为几个大型类型结构实现提供单一的事实来源,而不必联合泛型类的内部和外部。我的代码为自定义数据结构提供了一系列嵌套的别名,这基本上是一个由字典和列表组成的大JSON,其中"叶子"是"叶子"。是两种可能类型中的一种(称为AB),并且只有"A独占",或"Bexclusively"对于给定的树。然而,整个数据模型的一些子结构(如List[A]List[B])可能发生在字典树的不同层中,当它们的逻辑完全相同时,我不想为每一层维护特定的类型。

我将使用不变式List作为示例。我想有一个别名C = List[A | B]识别List[A]List[B]作为有效的子类型,而不必在任何地方做C = List[A] | List[B],因为工会需要歧视才能在我的效用函数中正常工作。我希望ABGenericAorB所取代,如果我有List[A]List[B]在我的实用程序中自动推断。

我从这里得到了灵感,因为这似乎是我所需要的:https://mypy.readthedocs.io/en/stable/generics.html type-variables-with-value-restriction

然而,它似乎不工作。

from typing import TypeVar, List
GenericStringOrFloat = TypeVar("GenericStringOrFloat", str, float)
ListStringOrFloat    = List[GenericStringOrFloat]
my_list : ListStringOrFloat
my_list = [12.0, 13.0]       # Fine
my_list = ["hello", "world"] # Fine
my_list = ["hello", 13.0]    # Also fine... what ? shouldn't mypy return an error for this ?

我不明白为什么最后一行没有返回错误。对我来说,它看起来像

GenericStringOrFloat = TypeVar("GenericStringOrFloat", str, float)

同义
GenericStringOrFloat = TypeVar("GenericStringOrFloat", str | float)

因此是完全没有意义的。这可能是我的bug吗?文档看起来这个语法正是为了避免这个问题,并使泛型不是哑Union。

我想要一个排他或,而不是包括,在给我的泛型类型限制的类型之间。用数学的方式表达(尽管不是严格的),我想要一种函子,它可以做类似ListUnionMapper : (A | B) -> List[A] | List[B]的事情(也适用于Dicts)。

你知道怎么做吗?

=========

编辑:

经过更多的实验,我找到了一个几乎可行的解决方案,但还远远不够完美。TLDR是你需要给泛型变量第二次,以允许mypy进行自动推理/区分;但是泛型类型限制仍然有一些奇怪的地方。

声明变量时,给它指定使用的特定类型(如strfloat),声明函数签名时,给它指定泛型类型变量。

下面的代码举例说明了我的意思:

from typing import TypeVar, List
GenericStringOrFloat  = TypeVar("GenericStringOrFloat", str, float)
ListFloat             = List[float]
ListString            = List[str]
ListStringOrFloat     = List[GenericStringOrFloat]
ListStringOrListFloat = ListString | ListFloat
my_list1A : ListStringOrFloat[      float] = [12.0,    13.0   ]  # Good as expected
my_list1B : ListStringOrFloat[      float] = ["hello", "world"]  # Error as expected: not float
my_list2A : ListStringOrFloat[str        ] = ["hello", "world"]  # Good as expected
my_list2B : ListStringOrFloat[str        ] = [12.0,    13.0   ]  # Error as expected: not str
my_list3  : ListStringOrFloat[str | float] = ["hello", 13.0   ]  # Good as expected
my_list4A : ListStringOrListFloat          = ["hello", "world"]  # Good as expected
my_list4B : ListStringOrListFloat          = [12.0,    13.0   ]  # Good as expected
my_list4C : ListStringOrListFloat          = ["hello", 13.0   ]  # Error as expected: not List[str] or List[float]
my_list4B                                  = ["hello", "world"]  # NB: doesn't infer/discriminate between List[str] and List[float], since union, so can be reassigned
my_list5A  : ListStringOrFloat[int]        = ["hello", 13.0   ]  # Error as expected: neither List[str} nor List[float]
my_list5B  : ListStringOrFloat[int]        = [1,       2      ]  # UNEXPECTED GOOD: this means that the type restriction basically doesn't work...
my_list_extra : ListStringOrFloat[GenericStringOrFloat] = ["hello", "world"]  # Error: Type variable GenericStringOrFloat is unbound

def return_first_element(lst: ListStringOrFloat[GenericStringOrFloat]) -> GenericStringOrFloat:
assert len(lst) > 0
return lst[0]

my_float_A : float = return_first_element(my_list1A)  # Good as expected
my_float_B : str   = return_first_element(my_list1A)  # Error: correctly infers that the function's return type is float
my_str_A   : float = return_first_element(my_list2A)  # Error: correctly infers that the function's return type is str
my_str_B   : str   = return_first_element(my_list2A)  # Good as expected
my_int_A   : float = return_first_element(my_list5B)  # Error: expects List[float] (and not List[Generic_Value]...)
my_int_B   : str   = return_first_element(my_list5B)  # Error: expression has type float + expects List[float] (and not List[Generic_Value]...)
my_int_C   : int   = return_first_element(my_list5B)  # Error: expression has type float + expects List[float] (and not List[Generic_Value]...)

问题不在于TypeVar,而是我如何解释my_list : ListStringOrFloat。因为没有指定元素类型,所以使用object。例如,您的代码也通过ListStringOrFloat[object],尽管我同意这似乎与GenericStringOrFloat的含义相矛盾。一般来说,单独的TypeVar没有什么意义,所以我并不太惊讶它会做一些奇怪的事情。

如果你只使用List[A] | List[B],你可以别名ListX = List[A] | List[B],然后使用ListX[str, float],这会引发["hello", 13.0]的错误。