from typing import Generic, TypeVar
T_co = TypeVar('T_co', covariant=True)
class CovariantClass(Generic[T_co]):
def get_t(self, t: T_co) -> T_co: # <--- Mypy: cannot use a covariant type variable as a parameter
return t
请参阅 已关闭的 mypy 问题
我对 PEP 的阅读 484 和 483 是协变函数 被禁止,但协变方法是允许的,并且 mypy 对其进行标记是“错误的”。也就是说,如果作者声明该类为协变,那么他们有责任确保该类中的任何协变方法表现良好(例如,不附加到协变项的集合) 。
来自 PEP 484:
考虑一个带有子类 Manager 的 Employee 类。现在假设我们有一个函数,其参数用 List[Employee] 注释。我们是否应该被允许使用 List[Manager] 类型的变量作为参数来调用这个函数?许多人会回答“是的,当然”,甚至不考虑后果。但除非我们更多地了解该函数,否则类型检查器应该拒绝这样的调用:该函数可能会将 Employee 实例追加到列表中,这将违反调用者中变量的类型。
事实证明,这样的参数是逆变的,而直观的答案(如果函数不改变它的参数,这是正确的!)要求参数是协变的。
...
协变或逆变不是类型变量的属性,而是使用该变量定义的泛型类的属性。方差仅适用于泛型类型;泛型函数没有这个属性。后者应该仅使用没有协变或逆变关键字参数的类型变量来定义。
然后它给出了带有协变参数的禁止函数的示例。
同样来自 PEP 483 作为反对在已关闭的 mypy 问题中提出的论点的证据,即禁止保持 python 类型安全(强调与 PEP 中一样):
可以声明用户定义的泛型类型的差异
PEP 484 包含一个在 __init__
中带有协变参数的类,但我在 PEP 中找不到任何示例方法中的协变参数,表明它是明确允许或禁止的。
T_co = TypeVar('T_co', covariant=True)
class ImmutableList(Generic[T_co]):
def __init__(self, items: Iterable[T_co]) -> None: ...
def __iter__(self) -> Iterator[T_co]: ...
...
编辑1:按照评论中的要求,说明这可能有用的简单示例。上述课程中来自 PEP 的额外方法:
def is_member(self, item: T_co) -> bool:
return item in self._items
编辑 2:另一个例子,以防第一个看起来很学术
def k_nearest_neighbours(self, target_item: T_co, k: int) -> list[T_co]:
nearest: list[T_co] = []
distances: list[float] = [item.distance_metric(target_item)
for item in self._items]
...
return nearest
from typing import Generic, TypeVar
T_co = TypeVar('T_co', covariant=True)
class CovariantClass(Generic[T_co]):
def get_t(self, t: T_co) -> T_co: # <--- Mypy: cannot use a covariant type variable as a parameter
return t
see closed mypy issue
My reading of the PEPs 484 and 483 is that covariant functions are prohibited but covariant methods are allowed and mypy is "wrong" to flag it. That's to say that if the class is declared by the author as covariant then it is their responsibility to ensure that any covariant method in the class is well-behaved (e.g. by not appending to collection of covariant items).
from PEP 484:
Consider a class Employee with a subclass Manager. Now suppose we have a function with an argument annotated with List[Employee]. Should we be allowed to call this function with a variable of type List[Manager] as its argument? Many people would answer "yes, of course" without even considering the consequences. But unless we know more about the function, a type checker should reject such a call: the function might append an Employee instance to the list, which would violate the variable's type in the caller.
It turns out such an argument acts contravariantly, whereas the intuitive answer (which is correct in case the function doesn't mutate its argument!) requires the argument to act covariantly.
....
Covariance or contravariance is not a property of a type variable, but a property of a generic class defined using this variable. Variance is only applicable to generic types; generic functions do not have this property. The latter should be defined using only type variables without covariant or contravariant keyword arguments.
It then gives an example of a prohibited function with a covariant argument.
Also from PEP 483 as evidence against the argument put forward in the closed mypy issue that it is prohibited to keep python type-safe (emphasis as in the PEP):
It is possible to declare the variance for user defined generic types
PEP 484 contains a class with a covariant argument in the __init__
but there is no example I could find in the PEPs of a covariant argument in a method showing it to be explicitly either allowed or prohibited.
T_co = TypeVar('T_co', covariant=True)
class ImmutableList(Generic[T_co]):
def __init__(self, items: Iterable[T_co]) -> None: ...
def __iter__(self) -> Iterator[T_co]: ...
...
EDIT 1: simple example of where this could be useful as requested in the comments. An extra method in the above class from the PEP:
def is_member(self, item: T_co) -> bool:
return item in self._items
EDIT 2: another example in case the first one seems academic
def k_nearest_neighbours(self, target_item: T_co, k: int) -> list[T_co]:
nearest: list[T_co] = []
distances: list[float] = [item.distance_metric(target_item)
for item in self._items]
...
return nearest
发布评论