Uri*_*nta 24 python abc python-3.x mypy python-typing
我遇到了一个可以通过交叉类型轻松解决的问题(目前正在讨论但尚未实施),并且想知道最干净的解决方法是什么。
我当前的设置大致对应于以下动物的 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: ...
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)
| 归档时间: |
|
| 查看次数: |
4054 次 |
| 最近记录: |