我有一套类似的类,称为"Executors",用于Strategy模式。它们是非常简单的类:
class ExecutorA:
def execute(self, data):
pass
class ExecutorB:
def execute(self, data):
pass
所有执行器的execute()
函数都需要以相同的方式执行,即以某种格式获取数据,并以另一种特定格式返回数据。由于鸭子打字,没有必要有基类,所以我没有写基类。
然而,我目前正在使用docstring等来记录我的代码,我认为在一个抽象基类中记录execute()
函数的数据格式要求可能是一个好主意,比如
class Executor(ABC):
def execute(self, data):
"""Process data in format xy and return data in format zy"""
pass
我的理由是,我不想在每个Executor类中复制相同的文档。这是ABC的常见用例吗?
澄清:我需要使用python 3.6,因为我们使用的是RHEL,而新的python还没有正式提供。
如果仅用于文档/静态类型检查,您还可以使用typing.Protocol
(从Python 3.8开始,通过typing_extensions
后端口(。这用于不需要显式继承的结构子类型。所以你可以做:
from typing import List, Protocol
class Executor(Protocol):
def execute(self, data: List[float]) -> float: # example type annotations
"""Reduce `data` to a single number."""
...
class ExecutorA: # no base class required, it implements the protocol
def execute(self, data: List[float]) -> float:
return sum(data)
def do_work(worker: Executor, # here we can use the Protocol class
data: List[float]) -> float:
return worker.execute(data)
do_work(ExecutorA(), [1., 2., 3.]) # this check passes
这里的文档字符串位于协议类上,提供execute
方法的一般信息。由于Executor
用于类型注释,用户将被引用到协议类。如果需要,还可以将附加信息添加到实现中(ExecutorA
,…(,或者可以复制原始文档字符串(这项工作可以由装饰器完成(。
使用抽象基类也是一种解决方案。ABC允许isinstance
和issubclass
检查,并且您可以注册不显式继承ABC的其他类。
值得一提的是,我认为在Python 3.6中使用抽象基类是一个合理的选择。你可能想把execute
装饰成@abstractmethod
,但是\_(ツ)_/。
现在,如果你想对你的文档字符串有更多的控制,你可以制作自己的元类,继承自ABCMeta
。例如,以下是一种具有"可扩展"文档字符串的方法,即您的文档字符串将成为格式字符串,其中{pdoc}
将始终被(第一个(父类的文档(如果存在(所取代。
from abc import abstractmethod, ABCMeta
from inspect import getdoc
class DocExtender(ABCMeta):
def __new__(cls, name, bases, spec):
for key, value in spec.items():
doc = getattr(value, '__doc__', '{pdoc}')
try:
pdoc = getdoc(getattr(bases[0], key))
except (IndexError, AttributeError):
pdoc = ''
try:
value.__doc__ = doc.format(pdoc=pdoc)
except AttributeError:
pass
return ABCMeta.__new__(cls, name, bases, spec)
class ExecutorBase(metaclass=DocExtender):
@abstractmethod
def execute(self, data):
"""
Parent
"""
pass
class Executor1(ExecutorBase):
def execute(self, data):
"""
{pdoc} - Child
"""
return sum(data)
class Executor2(ExecutorBase):
def execute(self, data):
return sum(data)
print(getdoc(Executor1.execute))
# Parent - Child
print(getdoc(Executor2.execute))
# Parent
我发布这篇文章主要是为了说明总体概念;显然,根据需要进行调整。