Ответ
Абстрактные базовые классы (Abstract Base Classes, ABC) из модуля abc позволяют определять "контракты" для дочерних классов, обязывая их реализовывать определенные методы.
Преимущества
- Гарантия реализации контракта: С помощью декоратора
@abstractmethodможно указать, какие методы обязательны для реализации в подклассах. Попытка создать экземпляр дочернего класса без реализации этих методов вызоветTypeError. - Явное наследование: Иерархия классов становится очевидной и легко отслеживаемой. Проверка
isinstance()иissubclass()работает ожидаемо. - Запрет на создание экземпляров: Нельзя создать объект самого абстрактного класса, что логично, так как он представляет собой неполную реализацию.
Недостатки
- Жесткая иерархия наследования: Класс должен явно наследоваться от ABC, что создает сильную связь. Это может быть неудобно, если класс уже является частью другой иерархии или если вы не можете изменять его исходный код.
- Более многословный синтаксис: Требуется импортировать
ABCиabstractmethod, а также явно указывать метакласс.
Пример
from abc import ABC, abstractmethod
# Абстрактный класс определяет интерфейс "Хранилище"
class BaseStorage(ABC):
@abstractmethod
def save(self, data: dict):
"""Сохраняет данные."""
pass
@abstractmethod
def load(self, item_id: str) -> dict:
"""Загружает данные."""
pass
# Конкретная реализация интерфейса
class FileStorage(BaseStorage):
def save(self, data: dict):
print(f"Сохранение {data} в файл...")
def load(self, item_id: str) -> dict:
print(f"Загрузка {item_id} из файла...")
return {"id": item_id, "data": "some_data"}
# Попытка создать экземпляр без реализации метода вызовет ошибку
class IncompleteStorage(BaseStorage):
def save(self, data: dict):
pass
# Метод load() не реализован
# fs = IncompleteStorage() # TypeError: Can't instantiate abstract class ...
fs = FileStorage()
fs.save({"id": "123"})
Альтернатива: Protocol
В современном Python для определения интерфейсов часто предпочитают typing.Protocol. Он реализует структурную типизацию (duck typing), проверяя наличие нужных методов без явного наследования, что обеспечивает большую гибкость.
Ответ 18+ 🔞
Так, слушай, смотри, вот есть в Питоне такая штука — абстрактные базовые классы, они же ABC. Из модуля abc, да. Ну, это типа такие заготовки, контракты, которые ты пишешь и говоришь: «Вот, блядь, все, кто от меня наследуется, обязаны реализовать вот эти методы, а иначе — пиздец, ошибка».
Что в этом хорошего, ёпта?
- Контракт, блядь, железный: Вешаешь декоратор
@abstractmethodна метод — и всё, при попытке создать объект дочернего класса без этого метода вылетитTypeError. Никаких «ой, забыл». Не реализовал — сиди, мудак, исправляй. - Всё прозрачно, как слёзы ребёнка: Иерархия классов видна невооружённым глазом.
isinstance()иissubclass()работают как часы, без сюрпризов. - Защита от дурака: Сам абстрактный класс инстанс создать нельзя. Ну логично же, зачем тебе полуфабрикат? Это просто план, чертёж, а не дом.
А что плохого, хитрая жопа?
- Жёсткая привязка, блядь: Класс должен прямо, в лоб, наследоваться от твоего ABC. А если он уже в другой иерархии сидит? Или код чужой, и ты его не трогаешь? Вот тут и начинается песня.
- Писать больше надо: Надо импортить, метаклассы указывать... В общем, мороки не на один чих.
Смотри, как это выглядит на практике:
from abc import ABC, abstractmethod
# Вот наш главный по тарелкам — абстрактное «Хранилище»
class BaseStorage(ABC):
@abstractmethod
def save(self, data: dict):
"""Сохраняет данные."""
pass
@abstractmethod
def load(self, item_id: str) -> dict:
"""Загружает данные."""
pass
# А вот конкретный работяга, который всё делает
class FileStorage(BaseStorage):
def save(self, data: dict):
print(f"Сохранение {data} в файл...")
def load(self, item_id: str) -> dict:
print(f"Загрузка {item_id} из файла...")
return {"id": item_id, "data": "some_data"}
# А вот ленивый распиздяй, который метод не дописал
class IncompleteStorage(BaseStorage):
def save(self, data: dict):
pass
# Метод load() забыл, сука!
# storage = IncompleteStorage() # Тут тебе и выедет: TypeError: Can't instantiate abstract class ... Пиздец и привет.
# А этот работает
fs = FileStorage()
fs.save({"id": "123"})
P.S. Вообще, сейчас модно использовать typing.Protocol для таких дел. Это структурная типизация, утиное типирование, ёпта. Проверяет не «кто твой папа» (наследование), а «что ты умеешь» (наличие методов). Гибче, блядь, в разы. Но это уже другая история, про которую можно овердохуища рассказать.