我正在尝试正确输入以下示例:
from __future__ import annotations
from dataclasses import dataclass
from typing_extensions import Protocol
@dataclass(frozen=True)
class Animal(Protocol):
age: int
@dataclass(frozen=True)
class Cat:
age: int
@dataclass(frozen=True)
class Dog:
age: int
class AnimalRepository(Protocol):
def get(self, animals: list[Animal]) -> None:
...
class CatRepository:
def get(self, animals: list[Cat]) -> None:
pass
class DogRepository:
def get(self, animals: list[Dog]) -> None:
pass
ANIMAL_TYPE_REPOSITORY_MAP: dict[type[Animal], AnimalRepository] = {
Cat: CatRepository(),
Dog: DogRepository(),
}
这会导致以下问题:
Expression of type "dict[type[Cat] | type[Dog], CatRepository | DogRepository]" cannot be assigned to declared type "dict[type[Animal], AnimalRepository]"
"CatRepository" is incompatible with protocol "AnimalRepository"
"get" is an incompatible type
Type "(animals: list[Cat]) -> None" cannot be assigned to type "(animals: list[Animal]) -> None"
Parameter 1: type "list[Animal]" cannot be assigned to type "list[Cat]"
"list[Animal]" is incompatible with "list[Cat]"
"DogRepository" is incompatible with protocol "AnimalRepository"
"get" is an incompatible type
Type "(animals: list[Dog]) -> None" cannot be assigned to type "(animals: list[Animal]) -> None"
因此,特定存储库无法正确实现
AnimalRepository
,因为我正在使用 Animal
协议的实现,即 Cat
、Dog
,而不是协议本身。
我能够在保持相同结构的情况下解决这个问题吗?
我怀疑这与里氏替换原则问题有关,但我不够熟悉,不知道如何处理这种情况。任何帮助将不胜感激。
尝试使用泛型和类型变量,但没有成功。
如所写,您的
CatRepository
和 DogRepository
不满足 AnimalRepository
。看一下CatRepository
,有以下方法。
def get(self, animals: list[Cat]) -> None:
但是
AnimalRepository
需要一个采用 list[Animal]
,而不是 list[Cat]
的方法。 AnimalRepository
说“我可以列出任何动物并做某事”,但你的方法只适用于猫,而不适用于所有动物。
您可以将
AnimalRepository
更改为通用。
T = TypeVar("T", bound=Animal)
class AnimalRepository(Protocol[T]):
def get(self, animals: list[T]) -> None:
...
现在
CatRepository
满足 AnimalRepository[Cat]
并且 DogRepository
满足 AnimalRepository[Dog]
。这些类型没有任何共同点,因为它们接受不同的参数,但至少所有代码都在同一个地方。
不幸的是,无法输入您的
ANIMAL_TYPE_REPOSITORY_MAP
。您正在寻找的是一个路径相关的地图。也就是说,您需要一个值的类型取决于键的值的映射。实施起来非常复杂。我们可以用像 Agda 这样的依赖类型证明语言来做到这一点,并且在像 Scala(带有 Shapeless)这样具有极其强大的类型系统的语言中“有点可能”。但从类型论的角度来看,Python 的类型提示是一个相当简单的系统。
这是我的推荐。您对界面有一个好主意。您想要一种方法来给出 Animal
的类型并获得
AnimalRepository[T]
,其中 T
就是该类型。我们可以用Python编写这个类型,但我们只是无法证明函数实现是正确的。所以我建议使用上面定义的class AnimalRepository(Protocol[T])
。然后使用 ANIMAL_TYPE_REPOSITORY_MAP
键入来定义您的 Any
。_ANIMAL_TYPE_REPOSITORY_MAP: dict[type[Animal], Any] = {
Cat: CatRepository(),
Dog: DogRepository(),
}
请注意,我在其前面添加了下划线,因为我们不想导出它。该字典现在是一个实现细节。
现在用一个函数公开它,消除类型问题。
def get_animal_repository(animal_type: type[T]) -> AnimalRepository[T]:
if animal_type in _ANIMAL_TYPE_REPOSITORY_MAP:
# Note: The Any type happily casts to whatever we want.
return _ANIMAL_TYPE_REPOSITORY_MAP[animal_type]
else:
# Your choice what to do in the case of an invalid animal.
...
这个功能的实现绝对是作弊的
Any
。但是调用者以及模块外部的任何人仍然会看到一个很好的类型安全 API。