Модуль abc в Python (Abstract Base Classes, абстрактные базовые классы) — это стандартная библиотека для описания интерфейсов: класс задаёт набор обязательных методов, но сам не создаёт объектов. Наследуете класс от ABC, помечаете методы декоратором @abstractmethod — и интерпретатор запрещает создать экземпляр, пока подкласс не реализует все абстрактные методы.

from abc import ABC, abstractmethod class Storage(ABC): @abstractmethod def save(self, data): ... Storage() # TypeError: Can't instantiate abstract class Storage # without an implementation for abstract method 'save'

Коротко о том, что даёт модуль:

  • Фиксирует контракт: подкласс обязан реализовать все помеченные методы, иначе объект не создастся.
  • Ошибка ловится в момент создания объекта (TypeError), а не при вызове несуществующего метода где-то глубоко в коде.
  • Абстрактными можно объявлять не только методы, но и свойства, classmethod и staticmethod.
  • Через register() к абстрактному классу можно «привязать» посторонние классы без наследования.
  • Это runtime-проверка. Если нужна только проверка типов статическим анализатором — берут typing.Protocol, а не abc.

Что такое абстрактный базовый класс и зачем он нужен

По данным docs.python.org.

Абстрактный класс — это заготовка интерфейса. Он описывает, что должен уметь объект, но не как именно. Реализацию дают конкретные подклассы. Смысл в том, чтобы зафиксировать общий договор для группы классов: любой обработчик платежей умеет pay(), любое хранилище умеет save() и load().

Без abc такой договор держится «на честном слове» — в комментариях и в голове разработчика. С abc он превращается в проверяемое правило: забыли реализовать метод — получили TypeError при первой же попытке создать объект. Особенно это ценно в командной работе и в библиотеках, где подклассы пишут другие люди.

Мини-вывод: abc переносит ошибку «класс не соответствует интерфейсу» из runtime глубоко в бою на момент создания объекта — раньше и дешевле.

ОНЛАЙН-ПРАКТИКУМ
ЗАПУСК нейросети DEEPSEEK R1 ЛОКАЛЬНО НА СВОЕМ КОМПЬЮТЕРЕ
ЧТО БУДЕТ НА ОБУЧЕНИИ?
  • ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросети DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
  • Где и как применять? Потестируем модель после установки на разных задачах
  • Как дообучить модель под себя?

ABC и ABCMeta: два способа объявить абстрактный класс в Python

Абстрактность в Python включается через метакласс ABCMeta. Есть два эквивалентных по результату способа его подключить.

Через наследование от ABC (рекомендуемый)

from abc import ABC, abstractmethod class Repository(ABC): @abstractmethod def get(self, key): ...

Класс ABC — это просто удобная обёртка с метаклассом ABCMeta, добавленная в Python 3.4. В большинстве кода используют именно её: короче и читается яснее.

Через metaclass=ABCMeta

from abc import ABCMeta, abstractmethod class Repository(metaclass=ABCMeta): @abstractmethod def get(self, key): ...

Этот вариант нужен, когда у класса уже есть свой метакласс и его требуется совместить с ABCMeta, либо когда важно явно управлять метаклассом. По документации, ABCMeta — это «metaclass for defining Abstract Base Classes», а сам механизм абстрактности живёт именно в нём, а не в ABC.

Мини-вывод: по умолчанию наследуйтесь от ABC; ABCMeta напрямую — только когда нужен нестандартный метакласс.

@abstractmethod: почему нельзя создать экземпляр абстрактного класса

Декоратор @abstractmethod помечает метод как обязательный к реализации. Правило из документации: класс с метаклассом, производным от ABCMeta, «cannot be instantiated unless all of its abstract methods and properties are overridden» — нельзя инстанцировать, пока не переопределены все абстрактные методы и свойства.

from abc import ABC, abstractmethod class Notifier(ABC): @abstractmethod def send(self, message): ... class EmailNotifier(Notifier): def send(self, message): print(f"email: {message}") class BrokenNotifier(Notifier): pass # метод send не реализован EmailNotifier().send("ok") # работает BrokenNotifier() # TypeError: Can't instantiate abstract class # BrokenNotifier without an implementation # for abstract method 'send'

Механика простая: @abstractmethod записывает имя метода в служебный набор __abstractmethods__ класса. Пока в этом наборе есть хоть одно имя, конструктор объекта поднимает TypeError. Как только подкласс переопределил метод, имя из набора уходит, и объект создаётся.

Важная деталь: абстрактный метод может иметь тело. Тогда реализация подкласса вправе вызвать родительскую логику через super().send(...) — abc не запрещает наследовать поведение, он лишь требует явного переопределения.

Мини-вывод: @abstractmethod — это не про «пустой метод», а про запись в __abstractmethods__, которая блокирует создание неполного объекта.

Абстрактные property, classmethod и staticmethod: порядок декораторов

Абстрактным можно объявить не только обычный метод. Ключевое правило из документации: @abstractmethod должен быть самым внутренним декоратором (innermost) — то есть стоять последним, ближе всего к def.

from abc import ABC, abstractmethod class Config(ABC): @property @abstractmethod def name(self): ... @classmethod @abstractmethod def from_file(cls, path): ... @staticmethod @abstractmethod def validate(raw): ...

Если перепутать порядок и поставить @abstractmethod сверху, абстрактность не сработает как надо, и класс можно будет инстанцировать без реализации. Поэтому связка всегда одна: сначала @property / @classmethod / @staticmethod, затем @abstractmethod.

Отдельные декораторы @abstractproperty, @abstractclassmethod и @abstractstaticmethod в модуле есть, но помечены как deprecated (с версий 3.2–3.3) — их заменили комбинацией с обычными декораторами. В новом коде их использовать не нужно.

Мини-вывод: запомните порядок «свойство/метод сверху, abstractmethod снизу» — это единственная нетривиальная часть синтаксиса.

register(): виртуальные подклассы без наследования

Метод register() позволяет объявить посторонний класс подклассом абстрактного — без реального наследования. Такой класс называют «виртуальным подклассом».

from abc import ABC class Sized(ABC): pass Sized.register(list) Sized.register(tuple) print(issubclass(tuple, Sized)) # True print(isinstance([1, 2], Sized)) # True

По документации, виртуальный подкласс «считается подклассом при проверке issubclass(), но не появляется в MRO и не получает реализаций методов ABC». То есть register() влияет только на проверки типа — он не отдаёт классу код и не заставляет реализовывать абстрактные методы. С версии 3.3 register() возвращает зарегистрированный класс, поэтому его можно применять как декоратор класса.

Зачем это нужно: подружить свой интерфейс со сторонними или встроенными типами, которые вы не можете переписать. Именно так устроен модуль collections.abc — встроенные list, dict, set зарегистрированы как подклассы абстракций Iterable, Sized и других.

Мини-вывод: register() — про совместимость по isinstance/issubclass, а не про передачу реализации.

__subclasshook__: автоматическое определение подклассов

Если ручной регистрации мало, поведение issubclass() настраивают через классовый метод __subclasshook__. Он вызывается при каждой проверке и возвращает True, False или NotImplemented (в последнем случае Python продолжает проверку обычным способом).

from abc import ABC, abstractmethod class Drawable(ABC): @abstractmethod def draw(self): ... @classmethod def __subclasshook__(cls, other): if cls is Drawable: if any("draw" in vars(base) for base in other.__mro__): return True return NotImplemented class Circle: # не наследует Drawable def draw(self): ... print(issubclass(Circle, Drawable)) # True — по наличию метода draw

Так работает структурная проверка «по факту наличия метода». На этом механизме построены абстракции из collections.abc: любой объект с методом __len__ считается Sized без всякой регистрации.

Мини-вывод: __subclasshook__ даёт «утиную» проверку типа внутри abc — совместимость определяется по структуре, а не по дереву наследования.

abc против typing.Protocol и duck typing: что выбрать

Как работает @abstractmethod и как выбрать между abc и Protocol
Как работает @abstractmethod и как выбрать между abc и Protocol

Три подхода к интерфейсам в Python решают одну задачу по-разному. Duck typing вообще не проверяет тип — просто вызывает метод и надеется, что он есть. abc проверяет контракт в runtime. typing.Protocol (структурная типизация из typing) проверяет контракт статически — при прогоне mypy или pyright, а не во время работы программы.

Критерий abc typing.Protocol duck typing
Когда проверяется В runtime, при создании объекта Статически (mypy / pyright) Не проверяется, ошибка при вызове
Нужно наследование Да (или register) Нет, по структуре Нет
Блокирует неполный класс Да, TypeError Нет, только предупреждение типизатора Нет
Совместимость с чужими типами Через register() Автоматически, если совпали методы Автоматически
Когда брать Библиотека/фреймворк, жёсткий контракт, общая реализация Чистая архитектура, статическая типизация, гибкость Скрипты, прототипы, простой код

Практическое правило: нужен runtime-запрет на неполные объекты и общий код для наследников — берите abc. Нужна проверка типов без навязывания наследования — Protocol. Пишете короткий скрипт, где формальный контракт избыточен — оставьте duck typing. Подходы не конкурируют жёстко: в больших проектах abc и Protocol часто соседствуют.

Мини-вывод: abc = runtime-контракт с наследованием; Protocol = статический контракт по структуре; duck typing = никакого контракта.

Практический пример: интерфейс платёжного шлюза

Соберём типичный сценарий из бэкенда — единый интерфейс для разных платёжных провайдеров. Абстрактный класс задаёт контракт, конкретные классы его реализуют, а остальной код работает с любым провайдером одинаково.

from abc import ABC, abstractmethod class PaymentGateway(ABC): @abstractmethod def charge(self, amount: int) -> bool: ... @abstractmethod def refund(self, txn_id: str) -> bool: ... def describe(self) -> str: # обычный метод, общий для всех return self.__class__.__name__ class StripeGateway(PaymentGateway): def charge(self, amount): return amount > 0 def refund(self, txn_id): return bool(txn_id) class YooKassaGateway(PaymentGateway): def charge(self, amount): return amount >= 100 def refund(self, txn_id): return True def process(gateway: PaymentGateway, amount: int) -> None: if gateway.charge(amount): print(f"{gateway.describe()}: списано {amount}") for gw in (StripeGateway(), YooKassaGateway()): process(gw, 500)

Функция process не знает, какой провайдер ей передали, — ей достаточно гарантии, что у объекта есть charge и refund. Добавили нового провайдера, забыли реализовать refund — получили TypeError при создании, а не сбой посреди платежа. Метод describe показывает, что в абстрактном классе спокойно живёт и обычный общий код.

ОНЛАЙН-ПРАКТИКУМ
ЗАПУСК нейросети DEEPSEEK R1 ЛОКАЛЬНО НА СВОЕМ КОМПЬЮТЕРЕ
ЧТО БУДЕТ НА ОБУЧЕНИИ?
  • ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросети DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
  • Где и как применять? Потестируем модель после установки на разных задачах
  • Как дообучить модель под себя?

Мини-проект: калькулятор площадей фигур

Классический пример на abc — набор геометрических фигур с общим контрактом area. Абстрактный класс Shape объявляет метод площади, конкретные фигуры его реализуют, а функция-калькулятор суммирует площади любого списка фигур.

from abc import ABC, abstractmethod import math class Shape(ABC): @abstractmethod def area(self): pass class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return math.pi * self.radius ** 2 class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height class Triangle(Shape): def __init__(self, base, height): self.base = base self.height = height def area(self): return 0.5 * self.base * self.height def calculate_total_area(shapes): return sum(shape.area() for shape in shapes) shapes = [Circle(10), Rectangle(5, 10), Triangle(10, 5)] print(f"Общая площадь: {calculate_total_area(shapes)}")

Здесь виден весь смысл abc: calculate_total_area работает с любой фигурой, лишь бы та наследовала Shape и реализовала area. Попытка создать Shape() напрямую завершится TypeError — абстрактный класс существует только как контракт.

Мини-вывод: один абстрактный контракт + разные реализации = код, который расширяется новыми фигурами без правок в калькуляторе.

Частые вопросы

Чем ABC отличается от ABCMeta?

ABCMeta — это метакласс, где живёт сам механизм абстрактности. ABC — удобная базовая обёртка над ним (появилась в Python 3.4). Наследование от ABC и указание metaclass=ABCMeta дают одинаковый результат; первый вариант короче и рекомендуется.

Какая ошибка возникает при создании абстрактного класса?

TypeError с сообщением вида «Can’t instantiate abstract class … without an implementation for abstract method ‘…’». Она поднимается в момент вызова конструктора, пока в наборе __abstractmethods__ остаются нереализованные методы.

Можно ли объявить абстрактное свойство?

Да. Нужно скомбинировать @property и @abstractmethod, причём @abstractmethod ставится последним (самым внутренним). Аналогично для @classmethod и @staticmethod. Старые декораторы @abstractproperty и подобные признаны устаревшими.

Что делает register() и когда он нужен?

register() объявляет посторонний класс виртуальным подклассом: он проходит isinstance/issubclass, но не наследует реализаций и не появляется в MRO. Нужен, чтобы подружить свой интерфейс со сторонними или встроенными типами, которые нельзя переписать.

abc или typing.Protocol — что выбрать?

abc — когда нужен runtime-запрет на неполные объекты и общая реализация для наследников. Protocol — когда достаточно статической проверки типов без навязывания наследования. Для простых скриптов часто хватает duck typing без формального интерфейса.

Может ли абстрактный метод иметь тело?

Да. Абстрактность требует лишь явного переопределения в подклассе, но реализация подкласса может вызвать родительскую логику через super(). Тело абстрактного метода — законный способ задать поведение по умолчанию.

для id="пайтон2" двойной блок курсов не обнаружен

Большой практикум
ЗАМЕНИ ВСЕ НЕЙРОСЕТИ НА ОДНУ — PERPLEXITY
ПОКАЖЕМ НА КОНКРЕТНЫХ КЕЙСАХ
  • Освой нейросеть Perplexity и узнай, как пользоваться функционалом остальных ИИ в одном
  • УЧАСТВОВАТЬ ЗА 0 РУБ.
  • Расскажем, как получить подписку
Участвовать бесплатно
ОНЛАЙН-ПРАКТИКУМ
ЗАПУСК нейросети DEEPSEEK R1 ЛОКАЛЬНО НА СВОЕМ КОМПЬЮТЕРЕ
ЧТО БУДЕТ НА ОБУЧЕНИИ?
  • ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросеть DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
Участвовать бесплатно