TL; DR; Да, для абстрактного класса нормально иметь неабстрактные методы.
Обычно то, что мы называем абстрактный класс - это просто класс, который не может быть создан.
С другой стороны, то, что мы называем интерфейсом - это класс, который имеет только объявления методов, но не имеет реализаций. В частности, это абстрактный класс, потому что он не имеет конструктора.
Конечно, в Python нет реальных интерфейсов: у каждого метода должно быть тело. Но мы можем несколько эмулировать интерфейсы через raise NotImplementedError()
.
В любом случае интерфейсы образуют подмножество абстрактных классов. Это, очевидно, предполагает, что существуют абстрактные классы, которые не являются интерфейсами. Это именно тот случай, который вы описываете. Да, абстрактный класс может содержать реализованные неабстрактные методы. И это неплохая практика. Это особенно полезно, когда данный метод не зависит от конкретной реализации.
Например, рассмотрим интерфейс для универсального синтаксического анализатора (я думаю о json.load
и json.loads
):
class ILoader(ABC):
@abstractmethod
def load(self, stream):
raise NotImplementedError()
Совершенно нормально дать loads
метод, который принимает строку вместо потока с реализацией по умолчанию:
class AbstractLoader(ABC):
@abstractmethod
def load(self, stream):
raise NotImplementedError()
def loads(self, text):
stream = io.StringIO(text)
return self.load(stream)
хотя я бы использовал префикс Abstract
вместо I
. ;)