Абстрактные классы в Python — ABC и abstractmethod

19 июня 2026
Автор

Антон Ларичев

Введение

Абстрактные классы — это классы, которые нельзя создать напрямую. Они задают общий интерфейс для группы дочерних классов, гарантируя, что каждый наследник реализует определённый набор методов. В Python абстрактные классы реализуются через модуль abc (Abstract Base Classes).

В этой статье разберём, зачем нужны абстрактные классы, как их создавать с помощью ABC и abstractmethod, как работать с абстрактными свойствами и где применять этот подход на практике.

Зачем нужны абстрактные классы

Без абстрактных классов ничего не мешает создать дочерний класс, не реализовав в нём нужные методы. Ошибка обнаружится только в момент вызова метода, а не при создании объекта:

class Shape:
    def area(self):
        raise NotImplementedError("Метод area() должен быть реализован")

class Circle(Shape):
    pass

# Ошибка возникнет только при вызове area()
c = Circle()  # Объект создастся без проблем
c.area()      # NotImplementedError

Абстрактные классы решают эту проблему — Python не позволит создать экземпляр класса, пока все абстрактные методы не будут реализованы.

Создание абстрактного класса с ABC

Чтобы создать абстрактный класс, нужно наследоваться от ABC и пометить нужные методы декоратором @abstractmethod:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        """Вычислить площадь фигуры"""
        pass

    @abstractmethod
    def perimeter(self):
        """Вычислить периметр фигуры"""
        pass

Теперь попытка создать экземпляр Shape или его наследника без реализации всех абстрактных методов вызовет TypeError:

# TypeError: Can't instantiate abstract class Shape
# with abstract methods area, perimeter
s = Shape()

Реализация абстрактных методов в наследниках

Каждый дочерний класс обязан реализовать все абстрактные методы:

from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

    # Обычный метод — не абстрактный, наследуется как есть
    def description(self):
        return f"{self.__class__.__name__}: площадь={self.area():.2f}, периметр={self.perimeter():.2f}"


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2

    def perimeter(self):
        return 2 * math.pi * self.radius


class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)


# Создаём объекты
circle = Circle(5)
rect = Rectangle(3, 7)

print(circle.description())  # Circle: площадь=78.54, периметр=31.42
print(rect.description())    # Rectangle: площадь=21.00, периметр=20.00

Обратите внимание: метод description() — обычный (не абстрактный). Он использует абстрактные методы area() и perimeter(), зная, что они точно будут реализованы в наследниках. Это мощный паттерн — шаблонный метод (Template Method).

Если вы хотите детальнее изучить объектно-ориентированное программирование и Python в целом — приходите на наш большой курс Python-разработчик с нуля. На курсе 180 уроков и 80 упражнений, AI-тренажёры для практики 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.

Абстрактные свойства

Декоратор @abstractmethod можно комбинировать с @property для создания абстрактных свойств:

from abc import ABC, abstractmethod

class Vehicle(ABC):
    @property
    @abstractmethod
    def max_speed(self):
        """Максимальная скорость транспорта"""
        pass

    @property
    @abstractmethod
    def fuel_type(self):
        """Тип топлива"""
        pass

    def info(self):
        return f"{self.__class__.__name__}: макс. скорость {self.max_speed} км/ч, топливо: {self.fuel_type}"


class Car(Vehicle):
    @property
    def max_speed(self):
        return 220

    @property
    def fuel_type(self):
        return "бензин"


class ElectricBike(Vehicle):
    @property
    def max_speed(self):
        return 45

    @property
    def fuel_type(self):
        return "электричество"


print(Car().info())          # Car: макс. скорость 220 км/ч, топливо: бензин
print(ElectricBike().info())  # ElectricBike: макс. скорость 45 км/ч, топливо: электричество

Порядок декораторов важен: @property должен стоять над @abstractmethod.

Использование ABCMeta напрямую

Вместо наследования от ABC можно использовать метакласс ABCMeta. Результат идентичный:

from abc import ABCMeta, abstractmethod

class Animal(metaclass=ABCMeta):
    @abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Гав!"

class Cat(Animal):
    def speak(self):
        return "Мяу!"

print(Dog().speak())  # Гав!
print(Cat().speak())  # Мяу!

Класс ABC — это просто удобная обёртка: class ABC(metaclass=ABCMeta): pass. Используйте ABCMeta напрямую, когда ваш класс уже наследуется от другого класса с собственным метаклассом.

Абстрактные классы и isinstance

Абстрактные классы отлично работают с проверкой типов через isinstance():

from abc import ABC, abstractmethod

class Exporter(ABC):
    @abstractmethod
    def export(self, data):
        pass

class JSONExporter(Exporter):
    def export(self, data):
        import json
        return json.dumps(data, ensure_ascii=False)

class CSVExporter(Exporter):
    def export(self, data):
        return "\n".join(",".join(str(v) for v in row) for row in data)


def process_export(exporter, data):
    # Проверяем, что передан правильный тип
    if not isinstance(exporter, Exporter):
        raise TypeError("Ожидается экземпляр Exporter")
    return exporter.export(data)


result = process_export(JSONExporter(), {"name": "Python"})
print(result)  # {"name": "Python"}

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

Рассмотрим реальный пример — система отправки уведомлений по разным каналам:

from abc import ABC, abstractmethod
from datetime import datetime

class Notifier(ABC):
    @abstractmethod
    def send(self, recipient, message):
        """Отправить уведомление"""
        pass

    @abstractmethod
    def validate_recipient(self, recipient):
        """Проверить корректность получателя"""
        pass

    def notify(self, recipient, message):
        """Шаблонный метод: валидация + отправка + логирование"""
        if not self.validate_recipient(recipient):
            raise ValueError(f"Некорректный получатель: {recipient}")
        self.send(recipient, message)
        print(f"[{datetime.now():%H:%M:%S}] Уведомление отправлено через {self.__class__.__name__}")


class EmailNotifier(Notifier):
    def send(self, recipient, message):
        print(f"Отправка email на {recipient}: {message}")

    def validate_recipient(self, recipient):
        return "@" in recipient and "." in recipient


class TelegramNotifier(Notifier):
    def send(self, recipient, message):
        print(f"Отправка в Telegram чат {recipient}: {message}")

    def validate_recipient(self, recipient):
        return recipient.startswith("@") or recipient.lstrip("-").isdigit()


# Полиморфное использование
notifiers = [EmailNotifier(), TelegramNotifier()]
for n in notifiers:
    n.notify("user@example.com" if isinstance(n, EmailNotifier) else "@user", "Привет!")

Частые ошибки

  • Забыть реализовать абстрактный метод — Python выбросит TypeError при попытке создать экземпляр. Сообщение об ошибке содержит список нереализованных методов — читайте его внимательно.

  • Неправильный порядок декораторов — при создании абстрактного свойства @property должен идти перед @abstractmethod. Если поставить наоборот, свойство не будет работать корректно.

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

  • Создание абстрактного класса без abstractmethod — класс, наследующий ABC без единого абстрактного метода, можно инстанцировать. Он не будет по-настоящему абстрактным.

Частозадаваемые вопросы

Можно ли создать экземпляр абстрактного класса? Нет. При попытке создать экземпляр класса, у которого есть хотя бы один нереализованный абстрактный метод, Python выбросит TypeError.

Чем ABC отличается от обычного класса с NotImplementedError? ABC с @abstractmethod ловит ошибку на этапе создания объекта, а NotImplementedError — только при вызове метода. Первый подход надёжнее, потому что проблема обнаруживается раньше.

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

Когда использовать ABCMeta вместо ABC? Когда ваш класс уже наследуется от класса с другим метаклассом. В остальных случаях наследование от ABC проще и предпочтительнее.

Заключение

Абстрактные классы — важный инструмент для построения чистой архитектуры в Python. Модуль abc с классом ABC и декоратором @abstractmethod позволяет явно определять контракты между базовыми и дочерними классами, предотвращая ошибки на ранних этапах. Используйте их, когда нужно гарантировать единый интерфейс для группы связанных классов.

Для закрепления навыков ООП в Python рекомендуем курс Python-разработчик с нуля. В первых 3 модулях курса доступно бесплатное содержание, что позволяет изучить базовый синтаксис и основы ООП и понять структуру курса до покупки полного доступа.

Стрелочка влевоЦелые числа в Python

Постройте личный план изучения Python до уровня Middle — бесплатно!

Python — часть карты развития Backend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Python

Как отправлять запросы с помощью requests в PythonПочему Python выводит значение без команды printКак работает команда print в PythonВозможности Python для автоматизации задачРабота с JSON в Python на примерахPython get — методы получения данныхКак находить и исправлять ошибки в PythonРабота с данными через API и внешние сервисыСтруктура и оформление кода PythonОсновы Django с PythonПолезные приёмы в Python для повседневной работыИспользование locals в Python для отладкиИнтеграция PHP и PythonКак выполнять HTTPS-запросы в PythonFastAPI Python — быстрый старт: создание REST API с нуляКак работать с API в Python
Ввод целого числа в PythonВедение логов в PythonУдаление данных в Python с помощью removeОбработка исключений с помощью try/except в PythonФункция super() в Python — как вызвать метод родителяСоздание собственных контекстных менеджеров в PythonРабота с символами программирования PythonРабота с переменной X в PythonРабота с классами в PythonКак скачать Python на компьютерПростая программа на Python для начинающихОсновы Python для тех, кто начинаетЧто нового в Python 3Поддерживается ли Python 2 и стоит ли его использоватьPython 1 — с чего начиналась история языкаКоманда python print - полное руководство по выводу данныхПравила именования переменных в PythonПользовательские исключения в PythonОсновы Python coreОписание объектов PythonНаследование классов в Python — основы и примерыМножественное наследование в Python — примеры и MROКонтекстный менеджер with в Python — как работает и зачем нуженКомментарии в Python — однострочные, многострочные и docstringКакой Python выбрать для установкиКак вывести целое число с помощью print в PythonКак установить Python на Windows macOS и LinuxКак пользоваться консолью PythonКак получить последний элемент в PythonКак найти значение в PythonКак настроить PythonКак использовать print для строк в PythonКак работает интерпретатор PythonИнструкция по работе с PythonБлок finally в обработке исключений PythonЦелые числа в PythonАбстрактные классы в Python — ABC и abstractmethod
Загрузка данных PythonУправление проектами на GitHub с PythonСоздание веб-приложений на Flask PythonСоздание бота на PythonСоздание интерфейсов Python QTСоздание игр с PygameСоздание GUI в PythonКак работать со словарями в PythonРабота с библиотеками через Python PackagingРабота со временем в Python при помощи модуля timePython name — особенности переменнойМатематические операции в Python с модулем mathPython listing — что это и как использоватьPytest — тестирование на Python: полное руководствоОбработка изображений с OpenCV PythonNumPy в Python — основы и применение в задачахМашинное обучение с PythonИспользование Anaconda с PythonМодуль contextlib в Python — утилиты для контекстных менеджеровБиблиотеки Python и их применение в проектах
Вложенные функции в PythonСоздание собственных декораторов в PythonВозврат значений из функции в PythonРабота с функцией map в PythonЦикл while в Python и примеры использованияОбработка чисел, введённых через input в PythonОсновные операторы в Python с примерамиУсловные выражения if else в Python для начинающихКак выполняется вызов функций call в PythonПродвинутые генераторы в Python — send, throw, close и корутиныПозиционные и именованные аргументы в PythonОбъявление переменных и управление областью видимости в PythonПередача аргументов по ссылке и по значению в PythonПередача аргументов через args и kwargs в PythonОсновные методы Python и примеры их использованияОператор match/case в Python 3.10+ — основы структурного сопоставленияПаттерны match/case в Python — деструктуризация, guard и вложенные шаблоныПрактические примеры match/case в Python — реальные сценарии примененияЛокальные и глобальные переменные в PythonЧасто используемые команды PythonКлючевые слова global и nonlocal в PythonКак создавать функции в PythonКак работает сборщик мусора в PythonКак работает область видимости переменных в PythonКак работает функция callable в PythonКак работает функция any и all в PythonКак проверить тип переменной в PythonКак передать функцию как аргумент в PythonКак использовать функцию isinstance в PythonКак использовать функцию filter в PythonКак использовать функцию filter в PythonКак использовать функцию eval безопасно в PythonКак использовать декораторы в PythonИзменяемые и неизменяемые типы данных в PythonГенераторы и yield в Python — как создавать и использоватьГенераторные выражения в Python — синтаксис и примерыФункции в Python и способы их вызоваФункции как объекты в PythonЧто такое замыкания в PythonЧто делает функция reduce в PythonЧто делает функция id в PythonАргументы по умолчанию в PythonАнонимные функции и lambda в PythonАлгоритмы на Python — примеры и объяснение
Запись данных в PythonУстановка pip в PythonУправление зависимостями requirement в PythonУправление библиотеками с помощью Python PackagingУдаление пробелов с помощью strip в PythonСтруктурирование кода в PythonСоздание исполняемого файла Python в exeРазбор traceback в модуле PythonРазбор site-packages в PythonРазбор Program Files в PythonРабота с Unicode кодировками в PythonРабота с системными функциями Python sysРабота с папкой AppData в PythonРабота с модулем logging в PythonРабота с каталогами в PythonРабота с CSV в PythonВиртуальная среда venv в Python — создание и настройкаКак создать простое приложение на PythonИспользование pip в Python для установки пакетовМодули в Python и организация кода в проектеИмпорт модулей в Python и правила подключенияРабота с файлами в Python пошаговоЧто делает компилятор Python и как он работаетПолучение строки из модуля PythonПодключение файлов в Python с includeПеременные среды в PythonСборка проекта с помощью packaging в PythonНастройка Python сервераИспользование Python на UbuntuИспользование консоли PythonИспользование кодировок в PythonИнициализация пакетов PythonИмпорт модулей PythonИмпорт имен в PythonСреда IDLE Python и базовые возможностиЧтение и запись TXT в PythonЧтение файлов в Python с помощью open file
Удаление элементов из списка PythonТипы данных в Python — обзор и рекомендацииОсновные операции со строками в PythonМетоды str в Python и обработка текстаСписки в Python и их ключевые методыСоздание списков данных в PythonРабота со строками и символами в PythonРабота со столбцами в PythonРабота со списком значений в PythonРабота с таблицами в Python с помощью DataFrameРабота с RFR в PythonРабота с пробелами в PythonРабота с массивами в PythonРабота с кортежами tuple PythonРабота с координатами X и Y в PythonРабота с ключами в PythonРабота с элементами данных PythonРабота с двоичными числами PythonРабота с данными в PythonРабота с данными NumPy PythonРабота с большими числами в PythonРабота с битами в PythonРабота с байтами в PythonЧто такое значение в Python и как его определитьМножества в Python и операции с нимиИспользование range в Python для цикловПроверка на четность в PythonПроверка числа в PythonПреобразование типов в PythonПреобразование списка в строку PythonПреобразование числа в строку в PythonПостроение графиков в PythonОпределение индекса элемента в PythonОкругление чисел в PythonОбъединение списков в Python с помощью zipМножества в PythonМассивы в Python и отличие от списковМассив чисел в PythonКортежи данных в PythonКак вычислить сумму чисел в PythonКак получить остаток от деления в PythonКак найти следующее число в PythonИспользование Unicode в PythonТип int в Python и его особенностиИндекс списка в PythonФункции для работы со строками в PythonЭлементы Python и способы доступа к нимДоступ к элементам массива в PythonДеление чисел в PythonРабота с данными в Python на практикеКак работать с числами в Python
Открыть базу знаний

Лучшие курсы по теме

Иконка молнииНовый
изображение курса

Основы Python

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий