我想在Python中创建一个抽象基类,其中合同的一部分是如何创建实例。不同的具体实现代表了可以互换使用的各种算法。下面是一个简化的示例(通常的免责声明-实际用例更复杂):
from abc import ABC, abstractmethod
from typing import Type
class AbstractAlgorithm(ABC):
@abstractmethod
def __init__(self, param: int):
pass
@abstractmethod
def get_result(self) -> int:
pass
class ConcreteAlgorithm(AbstractAlgorithm):
def __init__(self, param: int):
self._param = param
def get_result(self) -> int:
return self._param * 2
def use_algorithm(algorithm: Type[AbstractAlgorithm]) -> int:
a = algorithm(10)
return a.get_result()
上面的工作,但有缺点,我不能调用super().__init__(...)
在ConcreteAlgorithm.__init__
,这可能会破坏某些继承场景,我认为(纠正我,如果我错了,但调用super
是重要的多重继承,对吗?)。(严格来说,__init__
可以调用,但是与子类__init__
具有相同的签名,这没有意义)。
Python类是可调用的,所以我也可以这样表示:
from abc import ABC, abstractmethod
from typing import Callable
class AbstractAlgorithm(ABC):
@abstractmethod
def get_result(self) -> int:
pass
class ConcreteAlgorithm(AbstractAlgorithm):
def __init__(self, param: int):
self._param = param
def get_result(self) -> int:
return self._param * 2
def use_algorithm(algorithm: Callable[[int], AbstractAlgorithm]) -> int:
a = algorithm(10)
return a.get_result()
print(use_algorithm(ConcreteAlgorithm))
这可以工作,并且没有上面提到的缺点,但是我确实喜欢在抽象基类中使用__init__
签名,以用于文档目的。
最后,可以有抽象类方法,所以这种方法也可以工作:
from abc import ABC, abstractmethod
from typing import Type
class AbstractAlgorithm(ABC):
@classmethod
@abstractmethod
def initialize(cls, param: int) -> "AbstractAlgorithm":
pass
@abstractmethod
def get_result(self) -> int:
pass
class ConcreteAlgorithm(AbstractAlgorithm):
@classmethod
def initialize(cls, param: int) -> "ConcreteAlgorithm":
return cls(param)
def __init__(self, param: int):
self._param = param
def get_result(self) -> int:
return self._param * 2
def use_algorithm(algorithm: Type[AbstractAlgorithm]) -> int:
a = algorithm.initialize(10)
return a.get_result()
print(use_algorithm(ConcreteAlgorithm))
这是有效的,但我失去了使用algorithm
像一个可调用的好属性(它只是更灵活,如果有人真的想在一个函数中,例如,决定基于某些参数值使用哪个算法)。
那么,有没有一种方法可以满足这三个要求:
- 抽象基类中接口的完整文档。
- 可调用的具体实现。
- 没有不安全的行为,比如不能调用基类
__init__
。
严格来说可以调用
__init__
,但是与子类__init__
具有相同的签名,这没有意义。
不,它使完美有意义。
你规定了签名,因为你要求每个子类都精确地实现它。这意味着你也需要像那样调用。每个子类都需要根据抽象定义精确地调用其super().__init__
,并传递所有已定义的参数。