← Назад к курсу
Абстрактные классы в 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}")
Ключевые моменты:
- Декоратор @abstractmethod - помечает метод как абстрактный
- Класс должен наследоваться от ABC - чтобы стать абстрактным
- Невозможно создать экземпляр абстрактного класса
- Все абстрактные методы должны быть реализованы в дочерних классах
- Абстрактные классы могут содержать обычные методы, свойства и конструкторы
Преимущества абстрактных классов:
- Единый интерфейс - гарантия, что все дочерние классы реализуют определенные методы
- Полиморфизм - работа с разными классами через общий интерфейс
- Контроль архитектуры - задание структуры для семейства классов
- Защита от ошибок - Python не позволит создать экземпляр класса с нереализованными абстрактными методами
Когда использовать:
- Когда у вас есть несколько классов с общим поведением
- Когда нужно гарантировать реализацию определенных методов
- Когда вы разрабатываете библиотеку или framework
- Когда нужна четкая контрактная архитектура
Упражнение для закрепления:
Создайте абстрактный класс Animal с абстрактными методами:
- make_sound() - издать звук
- move() - способ перемещения
Создайте несколько конкретных классов (Dog, Bird, Fish), реализующих эти методы.