我遇到了一个问题,如果使用交叉类型(目前正在讨论但尚未实现),问题将很容易解决,我想知道最干净的解决方法是什么。
当前设置和问题
我的当前设置大致对应于以下动物的ABC层次结构。有一些动物的“特征”(CanFly
、CanSwim
等)定义为抽象子类(虽然它们也可以被定义为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: ...
通过这个定义,我可以区分抽象和具体的动物类别:
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和协议,但这涉及到重复编码,除非有某种方式可以从一个中定义另一个?是否有一种简洁的解决方案?
@abstractmethod
,则mypy需要实现协议方法才能实例化类。 - joel