← Назад к вопросам

Когда лучше создавать интерфейс на основе абстрактного класса в Python?

1.8 Middle🔥 151 комментариев
#Python Core#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когда использовать Abstract Base Class (ABC) в Python

Abstract Base Class (ABC) — это класс, который определяет интерфейс, который должны реализовать дочерние классы. Это способ задать контракт между базовым классом и его наследниками.

Основная идея

ABC гарантирует, что все дочерние классы реализуют необходимые методы:

from abc import ABC, abstractmethod

class Animal(ABC):  # Абстрактный класс
    @abstractmethod
    def make_sound(self) -> str:
        """Каждое животное должно издавать звук"""
        pass

class Dog(Animal):
    def make_sound(self) -> str:
        return 'Вау!'

class Cat(Animal):
    def make_sound(self) -> str:
        return 'Мяу!'

# Это вызовет ошибку — не реализованы все методы
# class Bird(Animal):
#     pass  # TypeError: Can't instantiate abstract class

# Правильно
dog = Dog()
cat = Cat()
print(dog.make_sound())  # Вау!
print(cat.make_sound())  # Мяу!

Когда использовать ABC

1. Когда вы разрабатываете иерархию классов в своём проекте

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount: float) -> bool:
        pass
    
    @abstractmethod
    def refund(self, transaction_id: str) -> bool:
        pass

class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> bool:
        print(f'Обработка платежа {amount} руб. по карте')
        return True
    
    def refund(self, transaction_id: str) -> bool:
        print(f'Возврат платежа {transaction_id}')
        return True

class PayPalProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> bool:
        print(f'Обработка платежа {amount} руб. через PayPal')
        return True
    
    def refund(self, transaction_id: str) -> bool:
        print(f'Возврат платежа {transaction_id}')
        return True

# Вы гарантируете, что любой процессор платежей реализует эти методы

2. Когда нужен общий базовый функционал для всех дочерних классов

from abc import ABC, abstractmethod
from datetime import datetime

class Repository(ABC):
    def __init__(self):
        self.created_at = datetime.now()
    
    @abstractmethod
    def get(self, id: int):
        pass
    
    @abstractmethod
    def save(self, obj):
        pass
    
    # Конкретный метод для всех дочерних классов
    def log_access(self, id: int) -> None:
        print(f'Доступ к {id} в {datetime.now()}')

class UserRepository(Repository):
    def get(self, id: int):
        return {'id': id, 'name': 'User'}
    
    def save(self, obj):
        print(f'Сохраняю: {obj}')

class ProductRepository(Repository):
    def get(self, id: int):
        return {'id': id, 'name': 'Product'}
    
    def save(self, obj):
        print(f'Сохраняю: {obj}')

# Оба класса имеют log_access
user_repo = UserRepository()
user_repo.log_access(1)  # Доступ к 1

3. Когда хотите контролировать расширяемость

from abc import ABC, abstractmethod

class Logger(ABC):
    @abstractmethod
    def log(self, level: str, message: str) -> None:
        """Логируй сообщение"""
        pass
    
    @abstractmethod
    def close(self) -> None:
        """Закрой ресурсы"""
        pass
    
    # Шаблонный метод
    def info(self, message: str) -> None:
        self.log('INFO', message)
    
    def error(self, message: str) -> None:
        self.log('ERROR', message)

class FileLogger(Logger):
    def __init__(self, filename: str):
        self.file = open(filename, 'a')
    
    def log(self, level: str, message: str) -> None:
        self.file.write(f'[{level}] {message}\n')
    
    def close(self) -> None:
        self.file.close()

class DatabaseLogger(Logger):
    def log(self, level: str, message: str) -> None:
        # Сохрани в БД
        print(f'Сохраняю в БД: [{level}] {message}')
    
    def close(self) -> None:
        # Закрой соединение
        pass

# Использование
logger = FileLogger('app.log')
logger.info('Приложение запущено')
logger.error('Ошибка!') 
logger.close()

ABC vs Protocol (сравнение)

КритерийABCProtocol
ИерархияЯвное наследованиеСтруктурная типизация
КонтрольСтрогий контрольГибкий подход
Внешние классыНужно переписыватьРаботает как есть
Конкретный кодМожно добавлятьТолько сигнатуры
Ошибки в runtimeПри создании экземпляраПри проверке типа
IDE поддержкаЛучшеХорошо

Примеры абстрактных свойств

from abc import ABC, abstractmethod

class User(ABC):
    @property
    @abstractmethod
    def email(self) -> str:
        pass
    
    @email.setter
    @abstractmethod
    def email(self, value: str) -> None:
        pass

class RegisteredUser(User):
    def __init__(self):
        self._email = ''
    
    @property
    def email(self) -> str:
        return self._email
    
    @email.setter
    def email(self, value: str) -> None:
        if '@' in value:
            self._email = value
        else:
            raise ValueError('Некорректный email')

user = RegisteredUser()
user.email = 'john@example.com'
print(user.email)  # john@example.com

Пример с множественным наследованием

from abc import ABC, abstractmethod

class Drawable(ABC):
    @abstractmethod
    def draw(self) -> None:
        pass

class Movable(ABC):
    @abstractmethod
    def move(self) -> None:
        pass

class GameObject(Drawable, Movable):
    def draw(self) -> None:
        print('Рисую объект')
    
    def move(self) -> None:
        print('Двигаю объект')

obj = GameObject()
obj.draw()  # Рисую объект
obj.move()  # Двигаю объект

Лучшие практики

  1. Используй ABC для иерархий, которые ты контролируешь
  2. Используй Protocol для интеграции внешнего кода
  3. Добавляй конкретные методы в ABC, если они общие для всех наследников
  4. Документируй требования в abstractmethod
  5. Не создавай глубокие иерархии — максимум 3-4 уровня

Когда выбрать ABC

┌─────────────────────────────────────┐
│ Выбирай ABC если:                   │
├─────────────────────────────────────┤
│ ✓ Разрабатываешь иерархию с нуля    │
│ ✓ Контролируешь все классы          │
│ ✓ Нужен общий функционал            │
│ ✓ Нужна строгая типизация           │
│ ✓ Хочешь ошибки при создании класса │
└─────────────────────────────────────┘

Итог: Abstract Base Class — мощный инструмент для определения контрактов в иерархии классов. Используй ABC, когда разрабатываешь собственные структуры, и Protocol, когда интегрируешь внешний код.