Шаг 71.
Python: тонкости программирования.
Классы и ООП. Абстрактные базовые классы держат наследование под контролем

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

    Абстрактные классы (АК), иногда также называемые абстрактными базовыми классами, гарантируют, что производные классы реализуют те или иные методы базового класса. На этом шаге вы узнаете о преимуществах абстрактных классов и о том, как их определять при помощи встроенного в Python модуля abc.

    Итак, в чем же прелесть абстрактных классов? Допустим, нужно определить простую иерархию классов для сервисного бэкенда самым благоприятным для программиста и удобным в сопровождении способом.

    Пусть имеется класс BaseService, который определял общий интерфейс и несколько конкретных реализаций. Конкретные реализации делают разные вещи, но все они обеспечивают тот же самый интерфейс (MockService, RealService и т. д.). Чтобы более четко проявить взаимосвязи, все конкретные реализации были производными от класса BaseService.

    Чтобы сделать этот программный код максимально удобным в обслуживании и благоприятным для программиста, мы хотели удостовериться, что

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

>>> class Base:
	def foo(self):
		raise NotImplementedError()
	def bar(self):
		raise NotImplementedError()

	
>>> class Concrete(Base):
	def foo(self):
		return 'вызвана foo()'
	# О нет, мы забыли переопределить bar()...
	# def bar(self):
	# return "вызвана bar()"

    Итак, что же мы получаем из этой первой попытки решения задачи? Вызов методов экземпляра Base правильно вызывает исключения NotImplementedError:

>>> b = Base()
>>> b.foo()
Traceback (most recent call last):
.   .   .
NotImplementedError

    Более того, и создание экземпляра, и использование Concrete работают так, как ожидалось. И если вызвать не реализованный в нем метод, такой как bar(), то в результате тоже будет вызвано исключение:

>>> c = Concrete()
>>> c.foo()
'вызвана foo()'
>>> c.bar()
Traceback (most recent call last):
.   .   .
NotImplementedError

    Эта первая реализация выглядит неплохо, но пока не идеально. Ее оборотными сторонами является то, что мы по-прежнему можем

    При помощи модуля Python abc, который был добавлен в Python 2.61, мы можем добиться большего успеха и решить эти оставшиеся проблемы.


См. документацию Python "Модуль abc": https://docs.python.org/3/library/abc.html.

    Вот обновленная реализация с использованием абстрактного класса, определенного в модуле abc:

>>> class Base(metaclass=ABCMeta):
	@abstractmethod
	def foo(self):
		pass
	@abstractmethod
	def bar(self):
		pass

	
>>> class Concrete(Base):
	def foo(self):
		pass
	# Мы снова забыли объявить bar()...

    Этот фрагмент кода по-прежнему ведет себя так, как нужно, и создает правильную иерархию классов:

>>> assert issubclass(Concrete, Base)

    С другой стороны, мы здесь получаем еще одно преимущество. Подклассы Base вызывают исключение TypeError во время создания экземпляра всякий раз, когда мы забываем реализовать какие-либо абстрактные методы. Вызванное исключение говорит о том, какой метод или методы отсутствуют:

>>> c = Concrete()
Traceback (most recent call last):
.   .   .
TypeError: Can't instantiate abstract class Concrete with abstract methods bar

    Без модуля abc мы получали бы только исключение NotImplementedError в случае фактического вызова отсутствующего метода. Возможность получать уведомления об отсутствующих методах во время создания экземпляра является большим преимуществом. В результате написание недопустимых подклассов в значительной степени блокируется. Возможно, этот факт не сыграет какой-то особой роли, если вы пишете новый код, но обещаем, что спустя несколько недель или месяцев он станет полезным.

    Безусловно, этот шаблон не является полной заменой проверки типов во время компиляции. Однако мы обнаружили, что он часто делает иерархии классов более прочными и более удобными в сопровождении. Использование абстрактных классов позволяет программисту четче формулировать свой замысел и таким образом делает код более коммуникативным. Рекомендуемю вам почитать документацию по модулю abc и присмотреться к ситуациям, где применение этого шаблона имеет смысл.

    На следующем шаге мы подитожим изученный материал.




Предыдущий шаг Содержание Следующий шаг