Миксины
Часть раздела Объектно-ориентированное программирование путешествия по Python на Coddy. Урок 38 из 64.
Миксины — это особый вид множественного наследования, используемый для добавления дополнительной функциональности в классы. Они предоставляют конкретные методы, сами по себе не являясь полноценными классами.
Вот пример простого примесьного класса:
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)Давайте разберём, что делает этот миксин:
self.__dict__— этот специальный атрибут содержит словарь всех атрибутов объекта и их значенийjson.dumps()— эта функция преобразует словарь Python в строку в формате JSON- Миксин «добавляет» эту функциональность сериализации в JSON в любой класс, который от него наследуется
Теперь посмотрим, как это работает:
class User(JSONSerializableMixin):
def __init__(self, name, email):
self.name = name
self.email = emailМиксин добавляет функциональность JSON любому классу, который наследуется от него:
user = User("Alice", "alice@example.com")
print(user.to_json())Вывод:
{"name": "Alice", "email": "alice@example.com"}Ключевое наблюдение: теперь у класса User есть метод to_json(), хотя он не был определён непосредственно в этом классе. Миксин «добавил» эту функциональность!
Создайте несколько примесей для реализации разных возможностей:
class PrintableMixin:
def pretty_print(self):
for key, value in self.__dict__.items():
print(f"{key}: {value}")
class ComparableMixin:
def __eq__(self, other):
return self.__dict__ == other.__dict__Каждый миксин обращается к self.__dict__, чтобы работать с атрибутами объекта, независимо от того, какой класс использует миксин. В этом и заключается сила миксинов: они предоставляют повторно используемые возможности, работающие с атрибутами любого класса.
Объедините несколько миксинов в одном классе:
class Product(JSONSerializableMixin, PrintableMixin, ComparableMixin):
def __init__(self, name, price):
self.name = name
self.price = price
product1 = Product("Laptop", 999)
product2 = Product("Laptop", 999)Используй все возможности примесей:
print(product1.to_json()) # Из JSONSerializableMixin
product1.pretty_print() # Из PrintableMixin
print(product1 == product2) # Из ComparableMixinВывод:
{"name": "Laptop", "price": 999}
name: Laptop
price: 999
TrueКлючевые характеристики примесей:
- Не предназначены для самостоятельного создания экземпляров
- Предоставляют определённые возможности для повторного использования
- Обычно не содержат методов
__init__ - Их названия часто заканчиваются на "Mixin" или "able"
- Могут сочетаться с множественным наследованием
- Для гибкости используют
self.__dict__или другие общие возможности объектов
Ключевой момент: примеси позволяют совместно использовать функциональность в разных иерархиях классов, не создавая сложных деревьев наследования. Они позволяют «подмешивать» определённые возможности, например сериализацию, сравнение или печать, в любой класс, которому они нужны. Это способствует повторному использованию кода и помогает классам сосредоточиться на своих основных задачах.
Задание
СреднеВ этом задании тебе предстоит реализовать простую систему электронной коммерции с помощью примесей и наследования.
Реализуй необходимые классы в этих файлах (следуй комментариям TODO в каждом файле):
printablemixin.py— создай PrintableMixin с функциональностью форматированного выводаdiscountmixin.py— реализуй DiscountMixin для расчёта скидок на товарыshippablemixin.py— создай ShippableMixin для работы с весом и стоимостью доставкиproduct.py— разработай базовый класс Product с соответствующим наследованием от примесейphysicalproduct.py— создай класс PhysicalProduct, расширяющий Productdigitalproduct.py— реализуй класс DigitalProduct с особым поведением при расчёте скидки
Попробуйте сами
from product import Product
from physicalproduct import PhysicalProduct
from digitalproduct import DigitalProduct
from printablemixin import PrintableMixin
from discountmixin import DiscountMixin
from shippablemixin import ShippableMixin
def test_basic_functionality():
# Проверить базовую функциональность всех классов
p = Product("Laptop", 1000)
assert p.print_details() == "Product: Laptop, Price: $1000", f"Print details failed: {p.print_details()}"
assert p.apply_discount(10) == 900, f"Discount calculation failed: {p.apply_discount(10)}"
physical = PhysicalProduct("Desk", 500)
physical.set_weight(30)
assert physical.calculate_shipping() == 15, f"Shipping calculation failed: {physical.calculate_shipping()}"
digital = DigitalProduct("Software", 200)
assert digital.apply_discount(10) == 180, f"Digital discount failed: {digital.apply_discount(10)}"
print("Basic functionality test passed!")
def test_edge_cases():
# Проверить граничные случаи, такие как нулевые и отрицательные значения
p = Product("Free Item", 0)
assert p.apply_discount(10) == 0, f"Zero price discount failed: {p.apply_discount(10)}"
p_neg = Product("Negative Item", -100)
assert p_neg.apply_discount(10) == -90, f"Negative price discount failed: {p_neg.apply_discount(10)}"
physical = PhysicalProduct("Empty Box", 10)
physical.set_weight(0)
assert physical.calculate_shipping() == 0, f"Zero weight shipping failed: {physical.calculate_shipping()}"
physical_neg = PhysicalProduct("Anti-Gravity Item", 10)
physical_neg.set_weight(-5)
assert physical_neg.calculate_shipping() == -2.5, f"Negative weight shipping failed: {physical_neg.calculate_shipping()}"
print("Edge cases test passed!")
def test_large_values():
# Проверить работу с очень большими значениями
p = Product("Expensive Item", 1000000)
assert p.apply_discount(10) == 900000, f"Large value discount failed: {p.apply_discount(10)}"
physical = PhysicalProduct("Heavy Item", 500)
physical.set_weight(1000)
assert physical.calculate_shipping() == 500, f"Large weight shipping failed: {physical.calculate_shipping()}"
print("Large values test passed!")
def test_inheritance():
# Проверить отношения наследования
p = Product("Test", 100)
physical = PhysicalProduct("Test", 100)
digital = DigitalProduct("Test", 100)
assert isinstance(p, PrintableMixin), "Product should inherit from PrintableMixin"
assert isinstance(p, DiscountMixin), "Product should inherit from DiscountMixin"
assert isinstance(physical, Product), "PhysicalProduct should inherit from Product"
assert isinstance(physical, ShippableMixin), "PhysicalProduct should inherit from ShippableMixin"
assert isinstance(physical, PrintableMixin), "PhysicalProduct should inherit from PrintableMixin through Product"
assert isinstance(physical, DiscountMixin), "PhysicalProduct should inherit from DiscountMixin through Product"
assert isinstance(digital, Product), "DigitalProduct should inherit from Product"
assert isinstance(digital, PrintableMixin), "DigitalProduct should inherit from PrintableMixin through Product"
assert isinstance(digital, DiscountMixin), "DigitalProduct should inherit from DiscountMixin through Product"
print("Inheritance test passed!")
def test_method_overriding():
# Проверить поведение при переопределении методов
p = Product("Regular Product", 100)
digital = DigitalProduct("Digital Product", 100)
# Одинаковая цена, одинаковый процент скидки, разные результаты
assert p.apply_discount(20) == 80, f"Regular discount calculation failed: {p.apply_discount(20)}"
assert digital.apply_discount(20) == 90, f"Digital fixed discount failed: {digital.apply_discount(20)}"
# Для цифрового продукта всегда должна применяться скидка 10% независимо от параметра
assert digital.apply_discount(0) == 90, "Digital product should apply 10% discount even with 0%"
assert digital.apply_discount(50) == 90, "Digital product should apply 10% discount even with 50%"
print("Method overriding test passed!")
def test_polymorphism():
# Проверить полиморфное поведение на списке продуктов разных типов
products = [
Product("Regular", 100),
PhysicalProduct("Physical", 100),
DigitalProduct("Digital", 100)
]
# Задать вес физического продукта
products[1].set_weight(10)
# Ожидаемые результаты для apply_discount(20)
expected_discounts = [80, 80, 90]
for i, product in enumerate(products):
# У всех должен быть метод print_details
assert "Product:" in product.print_details(), f"Polymorphic print_details failed for {type(product)}"
# У всех должен быть метод apply_discount, но с разными реализациями
assert product.apply_discount(20) == expected_discounts[i], f"Polymorphic apply_discount failed for {type(product)}"
print("Polymorphism test passed!")
def test_attribute_access():
# Проверка способов доступа к атрибутам
p = Product("Test Product", 100)
assert p.name == "Test Product", "Name attribute not properly set in Product"
assert p.price == 100, "Price attribute not properly set in Product"
physical = PhysicalProduct("Physical Product", 200)
assert physical.name == "Physical Product", "Name attribute not properly set in PhysicalProduct"
assert physical.price == 200, "Price attribute not properly set in PhysicalProduct"
# Атрибут weight не должен существовать до вызова set_weight
try:
weight = physical.weight
assert False, "Weight attribute should not exist before set_weight is called"
except AttributeError:
pass
physical.set_weight(15)
assert physical.weight == 15, "Weight attribute not properly set in PhysicalProduct"
digital = DigitalProduct("Digital Product", 300)
assert digital.name == "Digital Product", "Name attribute not properly set in DigitalProduct"
assert digital.price == 300, "Price attribute not properly set in DigitalProduct"
print("Attribute access test passed!")
# Основной блок запуска тестов
test_case = input()
if test_case == "basic_test":
test_basic_functionality()
elif test_case == "edge_cases":
test_edge_cases()
elif test_case == "large_values":
test_large_values()
elif test_case == "inheritance":
test_inheritance()
elif test_case == "method_overriding":
test_method_overriding()
elif test_case == "polymorphism":
test_polymorphism()
elif test_case == "attribute_access":
test_attribute_access()В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Введение в ООПВнешние файлыКлассы и объектыПараметр selfМетодыАтрибутыМетод-конструктор (__init__)Повторение — простой калькулятор4Наследование
Основы наследованияФункция super()Переопределение методовМножественное наследованиеПорядок разрешения методовПовторение: иерархия сотрудников7Специальные методы
Введение в магические методыПерегрузка операторовМагические методы контейнеровПовторение — пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн «Одиночка»Паттерн «Фабрика»Паттерн «Наблюдатель»Паттерн «Стратегия»2Декораторы
Введение в декораторыДекоратор свойстваДекоратор статического методаДекоратор метода класса5Полиморфизм
Переопределение методов: повторениеУтиная типизацияАбстрактные классы и методыПроектирование интерфейсовПовторение — калькулятор фигур8Продвинутые концепции ООП
Композиция и наследованиеМиксиныСтатические методы и методы классаДекораторы классовМенеджеры контекста11Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»3Свойства классов
Переменные экземпляра и классаДекораторы свойствПриватные атрибутыПовторение — менеджер банковского счёта6Инкапсуляция
Публичные, защищённые и приватные членыМодификаторы доступаСокрытие информацииПродвинутые декораторы свойствПовторение — система учёта студентов12Проект: Система управления библиотекой
Обзор проектаКлассы книг и пользователейСистема выдачи книгФункция поискаИнтерфейс администратораТестирование и интеграцияПотренируйтесь самостоятельно: Онлайн-компилятор Python