Лучший способ реализовать абстрактные классы в Python - PullRequest
1 голос
/ 17 марта 2020

Каков наилучший способ реализации абстрактных классов в Python?

Это основной подход, который я видел:

class A(ABC):
    @abstractmethod
    def foo(self):
        pass

Однако он не мешает вызывать абстрактный метод при расширении этого класса.

В Java вы получите ошибку, если попытаетесь сделать что-то подобное, но не в Python:

class B(A):
    def foo(self):
        super().foo()
B().foo() # does not raise an error

Для того, чтобы повторить то же самое поведение Java, вы могли бы принять этот подход:

class A(ABC):
    @abstractmethod
    def foo(self):
        raise NotImplementedError

Однако на практике я редко видел это последнее решение, даже если оно, по-видимому, является наиболее правильным. Есть ли конкретная c причина предпочитать первый подход, а не второй?

1 Ответ

2 голосов
/ 18 марта 2020

Если вы действительно хотите, чтобы ошибка возникала, если один из подклассов пытается вызвать абстрактный метод суперкласса, то да, вы должны вызвать его вручную. (а затем создайте экземпляр класса Exception для команды повышения raise NotImplementedError(), даже если он работает с классом напрямую)

Однако существующее поведение действительно удобно: если ваш абстрактный метод содержит только pass, тогда вы можете иметь любое количество подклассов, наследующих ваш базовый класс, и если хотя бы один из них реализует абстрактный метод, он будет работать. Даже если все они вызывают эквивалентный метод super () без проверки чего-либо еще.

Если в сложной иерархии будет вызвана ошибка - NotImplementedError или любая другая, использующая миксины, и например, вам нужно проверять каждый раз при вызове super, возникла ли ошибка, просто чтобы ее пропустить. Для записи возможно проверить, попадет ли super() в класс, где метод является абстрактным с условным условием, таким образом:

if not getattr(super().foo, "__isabstractmethod__", False):
     super().foo(...)

Так как вы хотите, если вы достигнете основы иерархии для метод состоит в том, чтобы ничего не делать, это очень просто, если просто ничего не происходит!

Я имею в виду, проверьте это:

class A(abc.ABC):
    @abstractmethod
    def validate(self, **kwargs):
        pass

class B(A):
    def validate(self, *, first_arg_for_B, second_arg_for_B=None, **kwargs):
        super().validate(**kwargs)
        # perform validation:
        ...

class C(A)
    def validate(self, *, first_arg_for_C **kwargs):
        super().validate(**kwargs)
        # perform validation:
        ...

class Final(B, C):
    ...

Ни B.validate, ни C.validate не нужно беспокоиться о любой другой класс в иерархии, просто делай свое дело и передавай дальше. Если A.validate вызовет повышение, оба метода должны будут выполнить super().validate(...) внутри оператора try: ...;except ...:pass или внутри странного if блока, чтобы получить ... ничего.

обновление - я только что нашел это примечание в официальной документации:

Примечание В отличие от Java абстрактных методов, эти абстрактные методы могут иметь реализацию. Эта реализация может быть вызвана через механизм super () из класса, который ее переопределяет. Это может быть полезно в качестве конечной точки для супер-вызова в платформе, которая использует кооперативное множественное наследование. https://docs.python.org/3/library/abc.html#abc .abstractmethod

Я даже верну вам личный вопрос, если вы можете ответить в комментариях: я понимаю, что он гораздо менее актуален в Java, где один не может иметь множественное наследование, поэтому даже в большой иерархии первый подкласс для реализации абстрактного метода обычно хорошо известен. Но в противном случае в проекте Java можно было бы выбрать один из различных базовых классов бетона и продолжить работу с другими в произвольном порядке, поскольку метод абстрактности повышается, как это разрешается?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...