仅将抽象基类用于文档目的



我有一套类似的类,称为"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允许isinstanceissubclass检查,并且您可以注册不显式继承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

我发布这篇文章主要是为了说明总体概念;显然,根据需要进行调整。

最新更新