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

标签 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)
简而言之,我喜欢继承 Zoo ,因此它只需要特定类型的 Animal。在这种情况下,DogHouse只有 Dog s。
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 (有点)私有(private)的,并丰富copy()的可变序列,所以 Zoo._items无法修改。
    有没有办法同时制作 DogHouse Zoo 的子类(并受益于 Zoo 中的通用方法),以及使用类型提示来验证我的代码不会意外允许猫或其他动物潜入狗屋?
    我已阅读 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.请参阅已接受的答案以获取正确答案,并解释为什么这是错误的。

    最佳答案

    您在之前的编辑中使用协变类型变量所做的尝试很接近,但类型变量不应该是协变的。使其成为协变意味着 Zoo[Dog]也是 Zoo[Animal] ,特别是,这意味着 add(self, animal: Animal_co)无论如何都可以带走任何动物Animal_co势必会。您正在寻找的行为确实是不变的,而不是协变的。 (您可能需要一个单独的“只读”动物园 ABC 或实际上是协变的协议(protocol)。)
    当你在做的时候,不要再戳 parent 的实现细节了:

    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]直接地。

    关于python - 如何在 Python 中为协变可变集合类类使用类型提示?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63089905/

    相关文章:

    python - docker 容器中的 PyCharm 远程调试

    scala - 如果 `K >: T` 是协变或逆变,那么 `K <: T` 和 `T` 的方差是多少?

    python - 如何在 for 循环中注释类型?

    python - "void"函数中的 NoReturn 与 None - Python 3.6 中的类型注释

    python - Jenkins Python API 和多配置项目

    python - 使用 Ctypes 从 Python 传递 C 结构指针

    python - 在 joblib `matlab` 上下文中腌制 `Parallel` 对象时出错

    c# - 如何在c#中实现泛型多态?

    java - 类的集合和继承

    python - 如何在类型提示中定义元组或列表的大小