Работая с сложными объектами, которые имеют похожее назначение, часто можно попасться на удочку дублирования. Решить подобную проблему, предоставить общий интерфейс классам и пришел модуль abc или Abstract Base Classes.
Кроме того, это отличный способ практиковать хорошие принципы проектирования и использования парадигм ООП.
Итак, абстрактный класс - это класс, в котором реализован хотя бы 1 абстрактный метод. Можно представить его как чертеж, по которому будут строиться дочерние классы. В свою очередь, абстрактный метод - это метод-заглушка, то есть без реализации и нужен он затем, чтобы показать, что этот метод обязательно должен быть реализован в всех дочерних классах.
Кроме того, в абстрактном классе могут присутствовать не только абстрактные методы, но и методы с конкретной реализацией, которые можно просто брать и использовать в дочерних классах.
Интересной особенностью абстрактного класса является то, что нельзя создать его экземпляр, только от дочерних классов. Кроме того, создать экземпляр дочернего класса не получится, если в нем не реализованы все абстрактные методы абстрактного базового класса.
Такие дела. А теперь пойдем смотреть, как это работает на практике.
Представим, что нам надо сделать общий интерфейс с какими-либо "методами" музыкальных инструментов. Ведь все они могут звучать, и должны быть отстроены перед игрой, только делается это по-разному. Попробуем реализовать это с помощью абстрактных классов:
from abc import ABC, abstractmethod
class MusicInstruments(ABC):
@abstractmethod
def make_sound(self):
raise NotImplementedError
@abstractmethod
def tune(self):
raise NotImplementedError
Абстрактный класс обязательно должен быть унаследован от abc.ABC.
Я обычно явно прописываю вызов исключений в абстрактных методах вместо ... или pass, чтобы ошибочные попытки обратиться к реализации в абстрактном классе увенчались провалом. Выглядит это так:
class Guitar(MusicInstruments):
def make_sound(self):
...
def tune(self):
super().tune()
gibson = Guitar()
gibson.tune()
> NotImplementedError
А что произойдет, если мы забудем реализовать все абстрактный методы в дочернем классе?
Ну во-первых, IDE подсветит этот момент:
А во-вторых, при попытке создать экземпляр такого класса, будет вызвано иключение TypeError:
class Guitar(MusicInstruments):
def make_sound(self):
...
gibson = Guitar()
> gibson = Guitar()
> ^^^^^^^^
> TypeError: Can't instantiate abstract class Guitar with abstract method tune
А теперь рассмотрим успешный пример реализации концепции базовых классов:
from abc import ABC, abstractmethod
class MusicInstruments(ABC):
@abstractmethod
def make_sound(self):
raise NotImplementedError
@abstractmethod
def tune(self):
raise NotImplementedError
class Guitar(MusicInstruments):
def make_sound(self):
print("Брынь")
def tune(self):
print("Все струны отстроены, давай играть!")
class Drums(MusicInstruments):
def make_sound(self):
print("Бдыщ!")
def tune(self):
print("Пластики подтянуты и отстроены, можно играть!")
guitar = Guitar()
drums = Drums()
guitar.tune()
drums.tune()
guitar.make_sound()
drums.make_sound()
> Все струны отстроены, давай играть!
> Пластики подтянуты и отстроены, можно играть!
> Брынь
> Бдыщ!
Ну вот, другое дело! Теперь наши классы построены по образу и подобию абстрактного класса MusicInstruments и мы стали на несколько шагов ближе к написанию качественного кода.
***
Присоединяйтесь ко мне в Telegram: https://t.me/python3_with_love. Там есть все, и читать код намного удобнее.