Mac*_*eek 5 python covariance type-hinting
我正在尝试在 Python 中创建一个类似协变集合的类,如下所示:
from typing import Generic, TypeVar, List, cast
class Animal():
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
class Zoo():
def __init__(self, items: List[Animal]):
self._items = items.copy() # type: List[Animal]
def add(self, animal: Animal) -> None:
self._items.append(animal)
def animal_count(self) -> int:
return len(self._items)
def get_animals(self) -> List[Animal]:
return self._items.copy()
class DogHouse(Zoo):
def __init__(self, items: List[Dog]):
self._items = items.copy() # type: List[Dog]
def add(self, dog: Dog) -> None:
assert isinstance(dog, Dog)
self._items.append(dog)
Run Code Online (Sandbox Code Playgroud)
简而言之,我喜欢子类化Zoo,这样它只需要特定类型的 Animal 。在本例中,aDogHouse仅包含Dogs。
Mypy 这段代码给出了两个错误:
error: Incompatible types in assignment (expression has type "List[Dog]", base class "Zoo" defined the type as "List[Animal]")error: Argument 1 of "add" is incompatible with supertype "Zoo"; supertype defines the argument type as "Animal"我理解 mypy 试图警告我的内容:以下代码片段在语法上是有效的,但可能会导致问题,因为 DogHouse 中可能突然出现另一种动物(猫、袋鼠等)(正式来说,该代码可能违反了里氏替换原则):
doghouse = DogHouse([])
doghouse._items.append(Cat())
Run Code Online (Sandbox Code Playgroud)
但是,我的代码应该解决这个问题,例如通过检查 中的类型DogHouse.add(),使其Zoo._items(在某种程度上)私有,并生成大量copy()可变序列,因此Zoo._items无法修改。
有没有一种方法既可以创建 的DogHouse子类Zoo(并从 中的通用方法中受益Zoo),又可以使用类型提示来验证我的代码不会意外地允许猫或其他动物潜入 DogHouse?
我已阅读https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generics,但无法将此建议应用到我的代码中(来自像 Python 这样的鸭子类型语言,我'我对协方差的概念还不是很详细)。
编辑:我尝试通过定义解决方案Animal_co = TypeVar('Animal_co', bound=Animal, covariant=True),但这会导致error: Cannot use a covariant type variable as a parameter.查看接受的答案以获得正确的答案,并解释为什么这是错误的。
您在早期编辑中使用协变类型变量所做的尝试很接近,但类型变量不应该是协变的。使其协变意味着 aZoo[Dog]也是 a Zoo[Animal],特别是,这意味着add(self, animal: Animal_co)无论Animal_co绑定什么,都可以采用任何 Animal 。您正在寻找的行为实际上是不变的,而不是协变的。(您可能需要一个单独的“只读”zoo ABC 或实际上是协变的协议。)
当你这样做时,不要再去探究父级的实现细节:
T = TypeVar('T', bound=Animal)
class Zoo(Generic[T]):
_items: List[T]
def __init__(self, items: Iterable[T]):
self._items = list(items)
def add(self, animal: T) -> None:
self._items.append(animal)
def animal_count(self) -> int:
return len(self._items)
def get_animals(self) -> List[T]:
return self._items.copy()
class DogHouse(Zoo[Dog]):
def add(self, dog: Dog) -> None:
assert isinstance(dog, Dog)
super().add(dog)
Run Code Online (Sandbox Code Playgroud)
用于assert在运行时进行类型检查。如果您实际上并不关心运行时强制,您可以简化DogHouse为
class DogHouse(Zoo[Dog]): pass
Run Code Online (Sandbox Code Playgroud)
或完全移除并Zoo[Dog]直接使用。