如何在Python中为类协变可变集合使用类型提示



我正试图在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)

简而言之,我喜欢将Zoo划分为子类,这样它只接受特定类型的Animal。在这种情况下,只有Dogs的DogHouse

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中突然可能有另一种动物(猫、袋鼠…((形式上,该代码可能违反Liskov替换原则(:

doghouse = DogHouse([])
doghouse._items.append(Cat())

然而,我的代码应该处理好这一点,例如,通过检查DogHouse.add()中的类型,使Zoo._items(有点(私有,并使可变序列的大量copy(),因此不能修改Zoo._items

有没有一种方法既可以使DogHouse成为Zoo的子类(并受益于Zoo中的泛型方法(,也可以使用类型提示来验证我的代码不会意外地允许猫或其他动物潜入DogHouse?

我读过https://mypy.readthedocs.io/en/stable/generics.html#variance-泛型,但很难将这些建议应用到我的代码中(来自像Python这样的鸭子类型的语言,我对协方差的概念还不是很详细(。


编辑:我试图通过定义Animal_co = TypeVar('Animal_co', bound=Animal, covariant=True)来解决问题,但这导致了error: Cannot use a covariant type variable as a parameter.。请参阅已接受的答案以获得正确答案,并解释为什么这是错误的。

您在早期编辑中使用协变类型变量的尝试很接近,但类型变量不应该是协变的。使其协变意味着Zoo[Dog]也是Zoo[Animal],特别是,这意味着无论Animal_co与什么结合,add(self, animal: Animal_co)都可以带走任何动物。你要寻找的行为实际上是不变的,而不是协变的。(你可能想要一个单独的"只读"动物园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)

assert用于在运行时进行类型检查。如果您实际上并不关心运行时强制,可以将DogHouse简化为

class DogHouse(Zoo[Dog]): pass

或者将其完全去除并直接使用CCD_ 21。

最新更新