← Назад к курсу

Абстрактные классы в Python

Что такое абстрактный класс?

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

Модуль abc

Для работы с абстрактными классами в Python используется модуль abc (Abstract Base Classes).

from abc import ABC, abstractmethod

1. Базовый пример

from abc import ABC, abstractmethod

# Создаем абстрактный класс
class Shape(ABC):
    
    @abstractmethod
    def area(self):
        """Вычислить площадь фигуры"""
        pass
    
    @abstractmethod
    def perimeter(self):
        """Вычислить периметр фигуры"""
        pass
    
    # Абстрактный класс может содержать обычные методы
    def description(self):
        return "Это геометрическая фигура"


# Создаем конкретный класс, наследуясь от абстрактного
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)


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return 3.14159 * self.radius ** 2
    
    def perimeter(self):
        return 2 * 3.14159 * self.radius


# Использование
if __name__ == "__main__":
    # Попытка создать экземпляр абстрактного класса вызовет ошибку
    # shape = Shape()  # TypeError: Can't instantiate abstract class Shape
    
    rect = Rectangle(5, 10)
    print(f"Прямоугольник: площадь = {rect.area()}, периметр = {rect.perimeter()}")
    print(rect.description())
    
    circle = Circle(7)
    print(f"Круг: площадь = {circle.area():.2f}, периметр = {circle.perimeter():.2f}")

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

from abc import ABC, abstractmethod, abstractproperty

class Vehicle(ABC):
    @abstractproperty
    def fuel_type(self):
        """Тип топлива транспортного средства"""
        pass
    
    @abstractmethod
    def max_speed(self):
        """Максимальная скорость"""
        pass
    
    @property
    def description(self):
        return "Транспортное средство"


class Car(Vehicle):
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model
    
    @property
    def fuel_type(self):
        return "Бензин"
    
    def max_speed(self):
        return 200
    
    @property
    def description(self):
        return f"{self.brand} {self.model}"


class ElectricCar(Vehicle):
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model
    
    @property
    def fuel_type(self):
        return "Электричество"
    
    def max_speed(self):
        return 180
    
    @property
    def description(self):
        return f"Электрический автомобиль {self.brand} {self.model}"


# Использование
if __name__ == "__main__":
    cars = [
        Car("Toyota", "Camry"),
        ElectricCar("Tesla", "Model 3")
    ]
    
    for car in cars:
        print(f"{car.description}: тип топлива - {car.fuel_type}, "
              f"макс. скорость - {car.max_speed()} км/ч")

3. Абстрактные статические методы и методы класса

from abc import ABC, abstractmethod, abstractstaticmethod, abstractclassmethod

class DatabaseConnector(ABC):
    
    @abstractstaticmethod
    def get_connection_string():
        """Получить строку подключения"""
        pass
    
    @abstractclassmethod
    def validate_config(cls, config):
        """Валидация конфигурации"""
        pass
    
    @abstractmethod
    def connect(self):
        """Подключиться к базе данных"""
        pass
    
    @abstractmethod
    def disconnect(self):
        """Отключиться от базы данных"""
        pass


class MySQLConnector(DatabaseConnector):
    
    @staticmethod
    def get_connection_string():
        return "mysql://user:pass@localhost/db"
    
    @classmethod
    def validate_config(cls, config):
        required = ['host', 'user', 'password', 'database']
        return all(key in config for key in required)
    
    def __init__(self, config):
        self.config = config
        self.connection = None
    
    def connect(self):
        # Здесь будет реальная логика подключения
        print(f"Подключаюсь к MySQL: {self.config['host']}")
        self.connection = "MySQL connection object"
        return True
    
    def disconnect(self):
        if self.connection:
            print("Отключаюсь от MySQL")
            self.connection = None


# Использование
if __name__ == "__main__":
    config = {
        'host': 'localhost',
        'user': 'admin',
        'password': 'secret',
        'database': 'mydb'
    }
    
    if MySQLConnector.validate_config(config):
        print("Конфигурация валидна")
    
    print(f"Строка подключения: {MySQLConnector.get_connection_string()}")
    
    connector = MySQLConnector(config)
    connector.connect()
    connector.disconnect()

4. Несколько абстрактных методов и миксины

from abc import ABC, abstractmethod

class Loggable(ABC):
    @abstractmethod
    def log(self, message):
        pass

class Serializable(ABC):
    @abstractmethod
    def to_dict(self):
        pass
    
    @abstractmethod
    def from_dict(self, data):
        pass


# Класс, реализующий несколько абстрактных классов
class User(Loggable, Serializable):
    def __init__(self, username, email):
        self.username = username
        self.email = email
        self._log = []
    
    def log(self, message):
        self._log.append(message)
        print(f"Лог для {self.username}: {message}")
    
    def to_dict(self):
        return {
            'username': self.username,
            'email': self.email,
            'log_count': len(self._log)
        }
    
    def from_dict(self, data):
        self.username = data['username']
        self.email = data['email']
        self._log = []
        return self
    
    def get_logs(self):
        return self._log.copy()


# Использование
if __name__ == "__main__":
    user = User("ivan", "ivan@example.com")
    user.log("Пользователь создан")
    user.log("Пользователь авторизовался")
    
    print(f"Данные пользователя: {user.to_dict()}")
    print(f"Количество записей в логе: {len(user.get_logs())}")

5. Практический пример: система оплаты

from abc import ABC, abstractmethod
from datetime import datetime

class PaymentMethod(ABC):
    @abstractmethod
    def process_payment(self, amount):
        """Обработать платеж"""
        pass
    
    @abstractmethod
    def refund(self, amount):
        """Вернуть средства"""
        pass
    
    @property
    @abstractmethod
    def currency(self):
        """Валюта платежа"""
        pass
    
    def get_payment_info(self):
        return f"Метод оплаты: {self.__class__.__name__}, валюта: {self.currency}"


class CreditCard(PaymentMethod):
    def __init__(self, card_number, expiry_date, cvv):
        self.card_number = card_number
        self.expiry_date = expiry_date
        self.cvv = cvv
        self._currency = "USD"
    
    @property
    def currency(self):
        return self._currency
    
    @currency.setter
    def currency(self, value):
        self._currency = value
    
    def process_payment(self, amount):
        # В реальной системе здесь была бы интеграция с платежным шлюзом
        print(f"Обработка платежа {amount} {self.currency} по карте "
              f"****{self.card_number[-4:]}")
        return {
            'success': True,
            'transaction_id': f"TXN{datetime.now().strftime('%Y%m%d%H%M%S')}",
            'amount': amount,
            'currency': self.currency,
            'timestamp': datetime.now()
        }
    
    def refund(self, amount):
        print(f"Возврат {amount} {self.currency} на карту "
              f"****{self.card_number[-4:]}")
        return {
            'success': True,
            'refund_id': f"REF{datetime.now().strftime('%Y%m%d%H%M%S')}",
            'amount': amount
        }


class PayPal(PaymentMethod):
    def __init__(self, email):
        self.email = email
        self._currency = "USD"
    
    @property
    def currency(self):
        return self._currency
    
    def process_payment(self, amount):
        print(f"Обработка платежа {amount} {self.currency} через PayPal "
              f"для {self.email}")
        return {
            'success': True,
            'transaction_id': f"PP{datetime.now().strftime('%Y%m%d%H%M%S')}",
            'amount': amount,
            'currency': self.currency
        }
    
    def refund(self, amount):
        print(f"Возврат {amount} {self.currency} через PayPal для {self.email}")
        return {
            'success': True,
            'refund_id': f"PPREF{datetime.now().strftime('%Y%m%d%H%M%S')}",
            'amount': amount
        }


class PaymentProcessor:
    def __init__(self):
        self.payment_methods = []
    
    def add_payment_method(self, method):
        if isinstance(method, PaymentMethod):
            self.payment_methods.append(method)
        else:
            raise TypeError("Метод должен быть экземпляром PaymentMethod")
    
    def process_order(self, amount, method_index=0):
        if method_index >= len(self.payment_methods):
            raise IndexError("Метод оплаты не найден")
        
        method = self.payment_methods[method_index]
        print(method.get_payment_info())
        return method.process_payment(amount)


# Использование
if __name__ == "__main__":
    # Создаем платежные методы
    card = CreditCard("4111111111111111", "12/25", "123")
    card.currency = "EUR"
    
    paypal = PayPal("user@example.com")
    
    # Создаем процессор платежей
    processor = PaymentProcessor()
    processor.add_payment_method(card)
    processor.add_payment_method(paypal)
    
    # Обрабатываем платежи
    print("=== Обработка платежей ===")
    
    # Платеж картой
    result1 = processor.process_order(100.50, 0)
    print(f"Результат: {result1}\n")
    
    # Платеж через PayPal
    result2 = processor.process_order(75.25, 1)
    print(f"Результат: {result2}\n")
    
    # Возврат средств
    print("=== Возврат средств ===")
    refund_result = card.refund(50.25)
    print(f"Результат возврата: {refund_result}")

Ключевые моменты:

  1. Декоратор @abstractmethod - помечает метод как абстрактный
  2. Класс должен наследоваться от ABC - чтобы стать абстрактным
  3. Невозможно создать экземпляр абстрактного класса
  4. Все абстрактные методы должны быть реализованы в дочерних классах
  5. Абстрактные классы могут содержать обычные методы, свойства и конструкторы

Преимущества абстрактных классов:

  1. Единый интерфейс - гарантия, что все дочерние классы реализуют определенные методы
  2. Полиморфизм - работа с разными классами через общий интерфейс
  3. Контроль архитектуры - задание структуры для семейства классов
  4. Защита от ошибок - Python не позволит создать экземпляр класса с нереализованными абстрактными методами

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

  • Когда у вас есть несколько классов с общим поведением
  • Когда нужно гарантировать реализацию определенных методов
  • Когда вы разрабатываете библиотеку или framework
  • Когда нужна четкая контрактная архитектура

Упражнение для закрепления:

Создайте абстрактный класс Animal с абстрактными методами:

  • make_sound() - издать звук
  • move() - способ перемещения

Создайте несколько конкретных классов (Dog, Bird, Fish), реализующих эти методы.