Утверждение и документация в классе для методов, ожидаемых в производных классах [дубликаты]

Скажем, я создаю класс с именем Bird, который я хочу использовать только как родительский класс, а производные классы должны иметь метод flap_wings:

class Bird:

    def fly(self):
        self.flap_wings()

Ожидаемый производный класс может выглядеть так:

class Eagle(Bird):

    def flap_wings(self):
        print('flapping wings')

Каков хороший и понятный способ для Bird одновременно утверждать, что его производные классы имеют метод flap_wings, а также включать документацию о том, что ожидается от flap_wings?

Прямо сейчас я использую __init_subclass__:

class Bird:

    def fly(self):
        self.flap_wings()

    def __init_subclass__(cls, **kwargs):
        assert hasattr(cls, 'flap_wings'), (
            "Derived classes have to have a flap_wings method which should "
            "print 'flapping wings'."
        )

Но выражение assert появляется только после создания класса Bird и не является «настоящей» строкой документации, к которой можно получить доступ через help.

Я знаю, что это открытый вопрос, но каковы другие лучшие способы? Это не противоречит правилам сначала определить flap_wings внутри Bird, возможно, только с телом pass и строкой документации. Но я просто не мог найти «стандартных» способов справиться с этой ситуацией. Поэтому я ищу любые предложения.


person Jay Calamari    schedule 21.06.2018    source источник
comment
Спасибо за ссылки на предыдущие вопросы. Я использовал __init_subclass__ с впечатлением, что это более простой способ сделать то, что здесь делают метаклассы, но я думаю, что __init_subclass__ просто не предлагает достаточно, чтобы делать то, что я хочу.   -  person Jay Calamari    schedule 21.06.2018


Ответы (1)


Вы можете использовать библиотеку abc для абстрактных методов:

from abc import ABCMeta, abstractmethod
import six

class Bird(six.with_metaclass(ABCMeta)):
    def fly(self):
        """Take flight.

        Notes
        -----
        This depends on the abstract method `flap_wings`. If you've
        not implemented this at the subclass level, your subclass
        cannot be properly instantiated.
        """
        self.flap_wings()

    @abstractmethod
    def flap_wings(self):
        """Subclasses must implement this"""

Это устанавливает своего рода контракт. Любой подкласс, который НЕ реализует метод flap_wings, вызовет ошибку при создании экземпляра:

class Flamingo(Bird):
    pass

>>> Flamingo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Flamingo with abstract methods flap_wings

Тогда как подкласс, реализующий абстрактный метод, будет работать нормально:

class BlueJay(Bird):
    def flap_wings(self):
        print("Flappity flap")

>>> BlueJay().fly()
Flappity flap

Что касается документирования подкласса, поскольку все подклассы наследуют метод fly, вы можете указать в его строке документации, что он вызывает метод flap_wings и ожидает его присутствия.

person Tgsmith61591    schedule 21.06.2018
comment
Спасибо за это, особенно показывая организацию docstrings. И хороший выбор птиц. Я буду использовать метаклассы. - person Jay Calamari; 21.06.2018