简体   繁体   English

Python 键入 - 约束列表只允许一种类型的子类

[英]Python typing - constrain list to only allow one type of subclass

I have 3 simple classes like:我有 3 个简单的类,例如:

class Animal(abc.ABC):
    ...

class Cat(Animal):
    ...

class Dog(Animal):
    ...

Then I have a function which is annotated as such:然后我有一个 function 注释如下:

def speak(animals: List[Animal]) -> List[str]:
   ...

My problem is that I want to constrain the List[Animal] to only include one type of animal, so:我的问题是我想限制List[Animal]只包含一种动物,所以:

speak([Dog(), Dog()]) # OK
speak([Cat(), Cat()]) # OK
speak([Cat(), Dog()]) # typing error

How would I annotate the speak function to allow for this?我将如何注释speak function 以允许这样做? Is it even possible to do using typing or am I forced to check this at runtime?甚至可以使用打字还是我被迫在运行时检查它?

I have tried to use the List[Animal] as above but that doesn't give me an error when calling speak like speak([Cat(), Dog()]) .我曾尝试使用List[Animal]如上所述,但是在调用speak speak([Cat(), Dog()])时不会给我错误。

I have also tried messing around with generics like TypeVar('T', bound=Animal) but this still allows me to pass in a List of any combination of subclasses.我也尝试过像TypeVar('T', bound=Animal)这样的 generics 乱搞,但这仍然允许我传入一个子类的任意组合的List

I would consider your issue to be not yet well defined.我认为您的问题尚未明确定义。 Once you start filling in a more concrete implementation of Animal , you're possibly going to arrive at a convincing solution.一旦您开始填写更具体的Animal实现,您可能会得出一个令人信服的解决方案。

Here, I'll reword your criteria for speak as it currently stands: You want it to accept a list of any individual subclass of Animal , but not Animal itself.在这里,我将重新表述您目前的speak标准:您希望它接受Animal的任何单个子类的list ,但不接受Animal本身。 Hopefully we can see why this doesn't make sense - there's nothing in your given code that suggests Animal can be distinguished from any subclass of Animal , at least from the point of view to what speak will do to the list of animals.希望我们能明白为什么这没有意义——您给定的代码中没有任何内容表明Animal可以Animal的任何子类区分开来,至少从speak对动物list的作用来看是这样。

Let's provide some distinguishing features instead:让我们提供一些与众不同的功能:

Python 3.10 Python 3.10

import typing as t
from typing_extensions import LiteralString
from collections.abc import Sequence

Sound = t.TypeVar("Sound", bound=LiteralString)

class Animal(t.Generic[Sound]):
    def speak(self) -> Sound:
        ...

class Cat(Animal[t.Literal["meow"]]):
    ...

class Dog(Animal[t.Literal["bark"]]):
    ...

def speak(animals: Sequence[Animal[Sound]]) -> list[str]:
    return [animal.speak() for animal in animals]


>>> speak([Dog(), Dog()])  # OK
>>> speak([Cat(), Cat()])  # OK
>>>
>>> # mypy: Argument 1 to "speak" has incompatible type "List[object]"; expected "Sequence[Animal[<nothing>]]" [arg-type]
>>> # pyright: Argument of type "list[Cat | Dog]" cannot be assigned to parameter "animals" of type "Sequence[Animal[Sound@speak]]" in function "speak"
>>> # pyre: Incompatible parameter type [6]: In call `speak`, for 1st positional only parameter expected `Sequence[Animal[Variable[Sound (bound to typing_extensions.LiteralString)]]]` but got `List[Union[Cat, Dog]]`
>>> speak([Cat(), Dog()])

Note that although mypy doesn't complain about the signature squeak(animals: list[Animal[Sound]]) , this is technically not type-safe;请注意,尽管mypy不会抱怨签名squeak(animals: list[Animal[Sound]]) ,但这在技术上不是类型安全的; you may decide to append Cat() to list[Dog] .您可以决定将 append Cat() list[Dog] This is why Sequence is used (it is non-mutable and covariant in its element types).这就是使用Sequence的原因(它的元素类型是不可变的和协变的)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM