我遇到了一个可以通过交集类型轻松解决的问题(目前正在讨论但尚未实施)并且想知道最干净的解决方法是什么。
我当前的设置大致对应于以下动物 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 和协议,尽管这涉及大量代码重复,除非有某种方法可以从另一个定义一个?
这一切有一个干净的解决方案吗?
它可能是 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()
使用继承和多重混合。 Python 专门为此目的支持多重继承。