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

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

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

当前设置和问题

我当前的设置大致对应于以下动物 ABC 层次结构。有许多动物“特征”(

CanFly
CanSwim
等)被定义为抽象子类(尽管它们也可以被定义为混入)。

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: ...

有了这个,我定义了具体的动物类别,包括抽象的和具体的:

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")

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

from typing import Generic, TypeVar

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

class Petter(Generic[T], ABC):

    @abstractmethod
    def pet(self, a: T) -> None:
        ...

但是,据我所知,无法为特征的交集指定 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

我可以尝试通过坚持

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: ...

有了这些我们确实可以定义一个有用的协议交集。将类型变量

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

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

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

python python-3.x mypy python-typing abc
2个回答
0
投票

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

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()

0
投票

使用继承和多重混合。 Python 专门为此目的支持多重继承。

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