Python 泛型缺少交集类型的解决方法?

Uri*_*nta 24 python abc python-3.x mypy python-typing

我遇到了一个可以通过交叉类型轻松解决的问题(目前正在讨论但尚未实施),并且想知道最干净的解决方法是什么。

当前设置和问题

我当前的设置大致对应于以下动物的 ABC 层次结构。有许多动物“特征”(CanFlyCanSwim等)被定义为抽象子类(尽管它们也可以被定义为 mixin)。

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def name(self) -> str: ...  
    
class CanFly(Animal):
    @abstractmethod
    def fly(self) -> None: ...
    
class CanSwim(Animal):
    @abstractmethod
    def swim(self) -> None: ...
Run Code Online (Sandbox Code Playgroud)

以此我定义了特定的动物类别,包括抽象的和具体的:

class Bird(CanFly):
    def fly(self) -> None:
        print("flap wings")
    
class Penguin(Bird, CanSwim):
    def name(self) -> str:
        return "penguin"
    def swim(self) -> None:
        print("paddle flippers")
Run Code Online (Sandbox Code Playgroud)

我还定义了一个通用类来抚摸特定类型的动物:

from typing import Generic, TypeVar

T = TypeVar("T", bound=Animal, contravariant=True)

class Petter(Generic[T], ABC):

    @abstractmethod
    def pet(self, a: T) -> None:
        ...
Run Code Online (Sandbox Code Playgroud)

然而,据我所知,没有办法为特征的交集指定 Petter:例如,对于所有既能飞又能游泳的动物。

class CanFlyAndSwim(CanFly, CanSwim):
    pass
        
class CanFlyAndSwimPetter(Petter[CanFlyAndSwim]):

    def pet(self, a: CanFlyAndSwim):
        a.name()
        a.fly()
        a.swim()
    
        
CanFlyAndSwimPetter().pet(Penguin())  # type error, as Penguin isn't a subclass of CanFlyAndSwim
Run Code Online (Sandbox Code Playgroud)

我可以尝试通过坚持Penguin显式继承来解决此问题CanFlyAndSwim,但这无法扩展到更多功能组合。

使用协议代替?

我尝试的另一种方法是使用协议:

from typing import Protocol

class AnimalProtocol(Protocol):
    def name(self) -> str: ...

class FlyProtocol(AnimalProtocol, Protocol):
    def fly(self) -> None: ...

class SwimProtocol(AnimalProtocol, Protocol):
    def swim(self) -> None: ...

Run Code Online (Sandbox Code Playgroud)

有了这些,我们确实可以定义一个有用的协议交集。将类型变量T上限更改为 后AnimalProtocol,我们可以编写:

class FlyAndSwimProtocol(FlyProtocol, SwimProtocol, Protocol):
    ...

class FlyAndSwimProtocolPetter(Petter[FlyAndSwimProtocol]):

    def pet(self, a: FlyAndSwimProtocol):
        a.name()
        a.fly()
        a.swim()
    
        
FlyAndSwimProtocolPetter().pet(Penguin())  # ok
Run Code Online (Sandbox Code Playgroud)

然而,用协议替换 ABC 会在定义动物时删除显式的类层次结构,这对于文档记录和检查是否已实现所有相关方法都很有用。我们可以尝试同时保留 ABC 和协议,尽管这涉及大量代码重复,除非有某种方法可以定义其中之一?

有没有一个干净的解决方案来解决这一切?

小智 1

它可能是关闭的......但我认为最好的方法是使用组合而不是继承。它将消除很多类的地狱并且变得更加简单。

from abc import ABC, abstractmethod
from dataclasses import dataclass


class AnimalFeature(ABC):
    @abstractmethod
    def action(self) -> None:
        ...


class CanFly(AnimalFeature):
    @staticmethod
    def action() -> None:
        print("flap wings")


class CanSwim(AnimalFeature):
    @staticmethod
    def action() -> None:
        print("paddle flippers")


@dataclass
class Pet:
    name: str
    features: list[AnimalFeature] | None

    def pet(self):
        print(self.name)

        for feature in self.features:
            feature.action()


peter_pet = Pet(
    name="Peter",
    features=[CanFly, CanSwim],
)
peter_pet.pet()
Run Code Online (Sandbox Code Playgroud)

  • 这不允许您注释一个仅接受可以飞行和游泳的参数的函数,这就是问题所在。它还使得调用函数变得更加困难(您需要在功能列表中搜索它,而不是调用“fly()”)。 (3认同)