Модуль 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 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
- Где и как применять? Потестируем модель после установки на разных задачах
- Как дообучить модель под себя?
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: что выбрать

Три подхода к интерфейсам в 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 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
- Где и как применять? Потестируем модель после установки на разных задачах
- Как дообучить модель под себя?
Мини-проект: калькулятор площадей фигур
Классический пример на 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(). Тело абстрактного метода — законный способ задать поведение по умолчанию.
- Освой нейросеть Perplexity и узнай, как пользоваться функционалом остальных ИИ в одном
- УЧАСТВОВАТЬ ЗА 0 РУБ.
- Расскажем, как получить подписку
- ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросеть DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ