Миксины
Часть раздела Object Oriented Programming путешествия по Python на Coddy — урок 38 из 64.
Миксины (примеси) — это особый вид множественного наследования, используемый для «подмешивания» дополнительной функциональности в классы. Они предоставляют определенные методы, не являясь при этом полноценными классами сами по себе.
Вот пример простой примеси (mixin):
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)Давайте разберем, что делает этот примесь (mixin):
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Основные характеристики примесей (mixins):
- Не предназначены для создания экземпляров сами по себе
- Предоставляют специфическую, повторно используемую функциональность
- Обычно не имеют методов
__init__ - Имена часто заканчиваются на «Mixin» или «able»
- Могут сочетаться с множественным наследованием
- Работают с
self.__dict__или другими общими функциями объектов для обеспечения гибкости
Ключевой момент: Миксины (Mixins) предоставляют способ совместного использования функциональности в различных иерархиях классов без создания сложных деревьев наследования. Они позволяют «подмешивать» определенные возможности, такие как сериализация, сравнение или печать, в любой класс, которому они необходимы. Это способствует повторному использованию кода и позволяет классам сосредоточиться на своих основных обязанностях.
Задание
СреднеВ этом испытании вы реализуете простую систему электронной коммерции, используя примеси (mixins) и наследование.
Реализуйте необходимые классы в этих файлах (следуйте комментариям TODO в каждом файле):
printablemixin.py— Создайте PrintableMixin с функциональностью форматированного выводаdiscountmixin.py— Реализуйте DiscountMixin для расчета снижения ценыshippablemixin.py— Создайте ShippableMixin для функций веса и стоимости доставкиproduct.py— Разработайте базовый класс Product с соответствующим наследованием примесейphysicalproduct.py— Создайте класс PhysicalProduct, расширяющий Productdigitalproduct.py— Реализуйте класс DigitalProduct со специальным поведением скидок
Шпаргалка
Примеси (Mixins) — это особый вид множественного наследования, используемый для «подмешивания» дополнительной функциональности в классы, при этом сами они не являются полноценными классами.
Базовый пример примеси:
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
class User(JSONSerializableMixin):
def __init__(self, name, email):
self.name = name
self.email = emailМожно комбинировать несколько примесей:
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__
class Product(JSONSerializableMixin, PrintableMixin, ComparableMixin):
def __init__(self, name, price):
self.name = name
self.price = priceКлючевые характеристики примесей:
- Не предназначены для создания экземпляров сами по себе
- Предоставляют специфическую, повторно используемую функциональность
- Обычно не имеют методов
__init__ - Имена часто заканчиваются на «Mixin» или «able»
- Могут комбинироваться с множественным наследованием
Попробуйте сами
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()В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыПараметр selfМетодыАтрибутыМетод-конструктор (__init__)Повторение — Простой калькулятор4Наследование
Базовое наследованиеФункция super()Переопределение методовМножественное наследованиеПорядок разрешения методовИтоги — Иерархия сотрудников7Специальные методы
Введение в магические методыПерегрузка операторовМагические методы контейнеровПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy5Полиморфизм
Снова о переопределении методовУтиная типизацияАбстрактные классы и методыПроектирование интерфейсовИтоги — Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияМиксиныСтатические методы и методы классаДекораторы классовМенеджеры контекста11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Свойства классов
Переменные экземпляра и классаДекораторы свойствПриватные атрибутыИтоги — Менеджер банковского счета6Инкапсуляция
Public, Protected, Private члены классаМодификаторы доступаСокрытие данныхДекораторы property: продвинутый уровеньИтоги: Система учета студентов12Проект: Управление библиотекой
Обзор проектаКлассы Book и User