【问题标题】:How to use type-hints for a covariant mutable collection-like class in Python?如何在 Python 中为协变可变集合类类使用类型提示?
【发布时间】:2020-07-26 07:29:04
【问题描述】:

我正在尝试在 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。在这种情况下,DogHouse 仅具有 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 试图警告我的内容:以下 sn-p 在语法上是有效的,但可能会导致问题,因为 DogHouse 中可能突然出现另一种动物(猫、袋鼠……)(正式地,该代码可能违反了 Liskov 替换原则):

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

但是,我的代码应该解决这个问题,例如通过检查DogHouse.add() 中的类型,将Zoo._items(在某种程度上)设为私有,并使可变序列的大量copy(),所以Zoo._items无法修改。

有没有办法让DogHouse 成为Zoo 的子类(并从Zoo 中的泛型方法中受益),以及使用类型提示来验证我的代码不会意外允许 Cats 或其他动物潜入狗屋?

我已阅读 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. 正确答案请参阅接受的答案,并解释为什么这是错误的。

【问题讨论】:

    标签: python covariance type-hinting


    【解决方案1】:

    您在之前的编辑中使用协变类型变量的尝试很接近,但类型变量不应该是协变的。使其成为协变意味着Zoo[Dog] 也是Zoo[Animal],特别是,这意味着add(self, animal: Animal_co) 可以接受任何动物,无论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
    

    或将其完全删除并直接使用Zoo[Dog]

    【讨论】:

    • 谢谢,我已经接近您的答案,但错误地定义了T = TypeVar('T', bound=Animal, covariant=True),导致error: Cannot use a covariant type variable as a parameter.。你的答案很完美。
    猜你喜欢
    • 2014-07-05
    • 2016-12-06
    • 2016-07-02
    • 2020-01-25
    • 2020-02-28
    • 1970-01-01
    • 1970-01-01
    • 2013-09-28
    • 2022-01-17
    相关资源
    最近更新 更多