Наследование vs Композиция
Когда что использовать?
Наследование (is-a):
· Используйте, когда один класс ЯВЛЯЕТСЯ специализацией другого
· "Собака является Животным"
· "Менеджер является Сотрудником"
Композиция (has-a):
· Используйте, когда один класс ИМЕЕТ другой класс как часть
· "Машина имеет Двигатель"
· "Дом имеет Комнату"
# Наследование там, где нужна композиция
class Car(Engine): # Машина НЕ "является" двигателем!
pass
# Композиция
class Car:
def __init__(self):
self.engine = Engine() # Машина "имеет" двигатель
# Композиция там, где нужно наследование
class Animal:
pass
class Dog:
def __init__(self):
self.animal = Animal() # Собака НЕ "имеет" животное!
# Наследование
class Dog(Animal): # Собака "является" животным
pass
👍3
Что такое декораторы классов?
Декораторы классов — это функции, которые принимают класс и возвращают модифицированный класс. Они позволяют изменять поведение класса без наследования, добавляя новую функциональность или изменяя существующую.
Как работают декораторы классов?
Декоратор класса получает исходный класс, может изменить его атрибуты, добавить новые методы или изменить существующие, и возвращает модифицированный класс.
def add_method(cls):
def greet(self):
return f"Привет, я {self.__class__.__name__}"
cls.greet = greet
return cls
@add_method
class Person:
pass
person = Person()
print(person.greet()) # Привет, я Person
👍3
Декораторы с параметрами
Иногда нам нужно передавать параметры в декоратор, чтобы настроить его поведение. Для этого создаем декоратор, который возвращает другой декоратор.
def repeater(num_of_repeats=1):
# Декоратор для повторения вызовов функции
def outer_decorator(func):
def inner_decorator(*args, **kwargs):
# Повторяем вызов num_of_repeats раз
for _ in range(max(1, num_of_repeats)):
print(func(*args, **kwargs))
return inner_decorator
return outer_decorator
👍3👏1
Метаклассы type
Метаклассы — это "классы классов". Они определяют, как создаются классы. В Python каждый класс создается метаклассом, и по умолчанию это встроенный метакласс type.
Как работает создание классов?
Когда вы пишете:
class MyClass:
x = 10
Python выполняет это так:
MyClass = type('MyClass', (), {'x': 10})Параметры функции type:
· name — имя класса
· bases — кортеж родительских классов
· attrs — словарь атрибутов класса
# Создаем класс динамически
MyDynamicClass = type('MyDynamicClass', (), {
'value': 42,
'get_value': lambda self: self.value,
'set_value': lambda self, new_value: setattr(self, 'value', new_value)
})
obj = MyDynamicClass()
print(obj.get_value()) # 42
obj.set_value(100)
print(obj.get_value()) # 100
👍3
Создание собственных метаклассов
Метаклассы позволяют перехватывать создание классов и модифицировать их до того, как они будут окончательно созданы. Это мощный инструмент для автоматизации повторяющихся задач.
class Meta(type):
def __new__(cls, name, bases, attrs):
attrs['created_by_meta'] = True
return super().__new__(cls, name, bases, attrs)
class MyClass(metaclass=Meta):
pass
print(MyClass.created_by_meta) # True
👍3
Практическое применение метаклассов
1. ORM (Object-Relational Mapping) системы
Метаклассы используются в Django ORM и SQLAlchemy для автоматического создания моделей базы данных на основе классов Python.
2. Веб-фреймворки
Фреймворки вроде Django используют метаклассы для автоматической регистрации моделей в админ-панели.
3. API фреймворки
FastAPI и Django REST Framework используют метаклассы для автоматической генерации документации API.
class PluginMeta(type):
registry = {}
def __new__(cls, name, bases, attrs):
new_class = super().__new__(cls, name, bases, attrs)
if name != 'Plugin':
cls.registry[name] = new_class
return new_class
class Plugin(metaclass=PluginMeta):
pass
class EmailPlugin(Plugin): pass
class SMSPlugin(Plugin): pass
print(PluginMeta.registry) # {'EmailPlugin': ..., 'SMSPlugin': ...}
👍3
Одиночка (Singleton)
Singleton — это порождающий паттерн проектирования, который гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру.
Зачем нужен Singleton?
Представьте ситуацию, когда у вас должно быть только одно подключение к базе данных, один логгер или один конфигурационный файл на все приложение. Singleton гарантирует, что не будет создано несколько экземпляров таких классов.
Проблема, которую решает Singleton:
Без Singleton разные части программы могут создавать свои собственные экземпляры класса, что приводит к:
· Расходованию лишней памяти
· Конфликтам при работе с ресурсами
· Несогласованному состоянию
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=Singleton):
pass
db1 = Database()
db2 = Database()
print(db1 is db2) # True
👍3
Фабрика (Factory)
Фабрика — это порождающий паттерн, который предоставляет интерфейс для создания объектов, но позволяет подклассам изменять тип создаваемых объектов. Фабрика инкапсулирует процесс создания объектов, делая код более гибким и расширяемым.
class Dog:
def speak(self): return "Гав!"
class Cat:
def speak(self): return "Мяу!"
class AnimalFactory:
@staticmethod
def create_animal(animal_type):
if animal_type == "dog": return Dog()
elif animal_type == "cat": return Cat()
animal = AnimalFactory.create_animal("dog")
print(animal.speak()) # Гав!
👍3
Наблюдатель (Observer)
Наблюдатель — это поведенческий паттерн, который позволяет объектам подписываться на события других объектов и автоматически получать уведомления об их изменении. Это создает механизм подписки для уведомления нескольких объектов об изменениях.
class NewsPublisher:
def __init__(self):
self._subscribers = []
def subscribe(self, subscriber):
self._subscribers.append(subscriber)
def publish_news(self, news):
for subscriber in self._subscribers:
subscriber.update(news)
class Subscriber:
def update(self, news):
print(f"Получена новость: {news}")
publisher = NewsPublisher()
subscriber = Subscriber()
publisher.subscribe(subscriber)
publisher.publish_news("Python 3.12 выпущен!")
👍3
Стратегия (Strategy)
Стратегия — это поведенческий паттерн, который определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Стратегия позволяет изменять алгоритмы независимо от клиентов, которые ими пользуются.
class PaymentStrategy:
def pay(self, amount): pass
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
return f"Оплата {amount} картой"
class ShoppingCart:
def __init__(self):
self.payment_strategy = None
def checkout(self, amount):
return self.payment_strategy.pay(amount)
cart = ShoppingCart()
cart.payment_strategy = CreditCardPayment()
print(cart.checkout(1000)) # Оплата 1000 картой
👍2👏1
Адаптер (Adapter)
Адаптер — это структурный паттерн, который позволяет объектам с несовместимыми интерфейсами работать вместе. Адаптер выступает прослойкой между двумя объектами, превращая вызовы одного в вызовы понятные другому.
from abc import ABC, abstractmethod
class DataProcessor(ABC):
@abstractmethod
def process(self, data): pass
class XMLProcessor:
def process_xml(self, xml_data):
return f"Обработка XML: {xml_data}"
class XMLAdapter(DataProcessor):
def __init__(self, xml_processor):
self.xml_processor = xml_processor
def process(self, data):
xml_data = f"<data>{data}</data>"
return self.xml_processor.process_xml(xml_data)
# Использование
xml_processor = XMLProcessor()
adapter = XMLAdapter(xml_processor)
print(adapter.process("John")) # Обработка XML: <data>John</data>
👍3🔥1
Декоратор (Decorator)
Декоратор — это структурный паттерн, который позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные «обёртки». Декоратор предоставляет гибкую альтернативу наследованию для расширения функциональности.
class Coffee:
def cost(self): return 2.0
def desc(self): return "Кофе"
class MilkDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self): return self.coffee.cost() + 0.5
def desc(self): return self.coffee.desc() + " + молоко"
class SugarDecorator:
def __init__(self, coffee):
self.coffee = coffee
def cost(self): return self.coffee.cost() + 0.3
def desc(self): return self.coffee.desc() + " + сахар"
# Использование
coffee = SugarDecorator(MilkDecorator(Coffee()))
print(f"{coffee.desc()} = {coffee.cost()} руб.")
# Кофе + молоко + сахар = 2.8 руб
👍4🔥1
Создание пользовательских исключений
Пользовательские исключения делают код более читаемым и позволяют точно идентифицировать ошибки вашего приложения, отличая их от стандартных исключений Python.
Базовый пользовательский класс исключения:
class BankError(Exception):
pass
class InsufficientFundsError(BankError):
def __init__(self, balance, amount):
super().__init__(f"Баланс {balance}, нужно {amount}")
class Account:
def __init__(self, balance=0):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
# Использование
acc = Account(100)
try:
acc.withdraw(200)
except InsufficientFundsError as e:
print(f"Ошибка: {e}") # Баланс 100, нужно 200
👍2
Наследование от встроенных исключений
Правильное наследование исключений:
В Python все исключения наследуются от базового класса Exception. При создании пользовательских исключений важно правильно выбрать родительский класс для совместимости.
class ValidationError(ValueError):
"""Наследуем от ValueError для совместимости"""
pass
def validate_age(age):
if age < 0:
raise ValidationError("Возраст не может быть отрицательным")
try:
validate_age(-5)
except ValueError as e: # ✅ Поймает ValidationError
print(f"Ошибка: {e}")
👍2
Обработка исключений в методах
Два подхода:
1. Обработка внутри метода
2. Проброс исключения наружу
Пример:
class FileReader:
def read_safe(self, filename):
"""Обработка внутри метода"""
try:
with open(filename) as f:
return f.read()
except FileNotFoundError:
return "" # Возвращаем значение по умолчанию
def read_strict(self, filename):
"""Проброс исключения наружу"""
with open(filename) as f:
return f.read() # FileNotFoundError пройдет выше
reader = FileReader()
content = reader.read_safe("нет_файла.txt") # Вернет ""
# reader.read_strict("нет_файла.txt") # Вызовет FileNotFoundError
👍1👏1
Контекстные менеджеры с классами
Классы с enter и exit:
Позволяют создавать свои контекстные менеджеры.
Пример:
class Timer:
"""Измеряет время выполнения блока кода"""
def __enter__(self):
import time
self.start = time.time()
def __exit__(self, *args):
import time
print(f"Выполнено за: {time.time() - self.start:.2f} сек.")
with Timer():
import time
time.sleep(1) # Вывод: Выполнено за: 1.00 сек.
👍2
Паттерн "Фабрика исключений"
Централизованное создание исключений:
Упрощает управление ошибками в больших проектах.
Пример:
class ExceptionFactory:
"""Создает стандартизированные исключения"""
@staticmethod
def validation_error(field, message):
return ValueError(f"[VALIDATION] {field}: {message}")
@staticmethod
def business_error(code, message):
error = RuntimeError(message)
error.code = code
return error
# Использование
try:
raise ExceptionFactory.validation_error("email", "Неверный формат")
except ValueError as e:
print(f"Ошибка: {e}") # [VALIDATION] email: Неверный формат
👍2
Модуль unittest для тестирования классов
Базовое тестирование:
Unittest — встроенный модуль для тестирования.
Пример:
import unittest
class Calculator:
def add(self, a, b):
return a + b
class TestCalculator(unittest.TestCase):
def setUp(self):
"""Подготовка перед каждым тестом"""
self.calc = Calculator()
def test_add_positive(self):
self.assertEqual(self.calc.add(2, 3), 5)
def test_add_negative(self):
self.assertEqual(self.calc.add(-1, -1), -2)
if __name__ == "__main__":
unittest.main()
👍2
Mock объекты для тестирования
Изоляция зависимостей:
Mock-объекты имитируют реальные объекты для тестирования.
Пример:
from unittest.mock import Mock
class WeatherService:
def __init__(self, api):
self.api = api
def get_temp(self, city):
return self.api.fetch(city)['temp']
# Тест
mock_api = Mock()
mock_api.fetch.return_value = {'temp': 25}
service = WeatherService(mock_api)
result = service.get_temp("Москва")
assert result == 25
mock_api.fetch.assert_called_once_with("Москва")
👍1👏1
Тестирование наследования
Проверка полиморфного поведения:
Тестируем, что дочерние классы правильно переопределяют методы.
Пример:
class Animal:
def speak(self):
return "Звук"
class Dog(Animal):
def speak(self):
return "Гав"
class TestAnimals(unittest.TestCase):
def test_inheritance(self):
dog = Dog()
self.assertEqual(dog.speak(), "Гав")
self.assertIsInstance(dog, Animal)
👍1👏1
