Использование абстрактного базового класса только для документации - PullRequest
3 голосов
/ 12 марта 2020

У меня есть набор похожих классов, называемых «Исполнители», которые используются в шаблоне стратегии . Это очень простые классы:

class ExecutorA:
  def execute(self, data):
    pass

class ExecutorB:
  def execute(self, data):
    pass

Функции execute() всех исполнителей должны вести себя одинаково, то есть принимать данные в определенном формате и возвращать данные в другом определенном формате. Из-за утки, нет необходимости иметь базовый класс, поэтому я его не написал.

Однако в настоящее время я документирую свой код, используя docstrings et c. и я подумал, что было бы неплохо задокументировать требования к формату данных для функций execute() в абстрактном базовом классе, например:

class Executor(ABC):
  def execute(self, data):
  """Process data in format xy and return data in format zy"""
    pass

Мое обоснование заключается в том, что я не хочу копировать та же документация в каждом классе Executor. Является ли это распространенным случаем использования для AB C?

Пояснение: Мне нужно использовать python 3.6, потому что мы используем RHEL, а более новый python еще нет официально доступны.

Ответы [ 2 ]

1 голос
/ 15 марта 2020

Я думаю, что для этого стоит использовать абстрактный базовый класс в 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

Я публикую это в основном для иллюстрации общей концепции; отрегулируйте по мере необходимости, очевидно.

1 голос
/ 12 марта 2020

Если это только для целей проверки документации / состояния c, вы также можете использовать 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

Строка do c здесь относится к классу протокола, предоставляя общую информацию о том, что делает метод execute. Поскольку Executor используется для аннотаций типов, пользователи будут отнесены к классу протокола. При желании можно также добавить дополнительную информацию к реализациям (ExecutorA, ...) или скопировать оригинальную строку do c (эту работу может выполнить декоратор).

Использование абстрактных базовых классов также является решением. ABC допускают проверки isinstance и issubclass, и вы можете зарегистрировать дополнительные классы, которые явно не наследуют AB C.

...