Python 类型:使用另一个协议作为参数的方法实现协议的具体类

问题描述 投票:0回答:1

我正在尝试正确输入以下示例:

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
,而不是协议本身。

我能够在保持相同结构的情况下解决这个问题吗?

我怀疑这与里氏替换原则问题有关,但我不够熟悉,不知道如何处理这种情况。任何帮助将不胜感激。

尝试使用泛型和类型变量,但没有成功。

python protocols repository-pattern typing
1个回答
0
投票

如所写,您的

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。

    

© www.soinside.com 2019 - 2024. All rights reserved.