Миксины
Часть раздела Объектно-ориентированное программирование путешествия по Python на Coddy. Урок 38 из 64.
Mixins — это особый вид множественного inheritance, используемый для «добавления» дополнительной functionality в классы. Они предоставляют определённые methods, сами при этом не являясь полноценными классами.
Вот пример простого mixin:
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)Разберём, что делает этот mixin:
self.__dict__— этот специальный attribute содержит словарь всех attribute объекта и их значенийjson.dumps()— эта функция преобразует словарь Python в строку в формате JSON- mixin «смешивает» эту функциональность сериализации JSON с любым class, который inherits от него
Теперь давайте посмотрим, как это работает:
class User(JSONSerializableMixin):
def __init__(self, name, email):
self.name = name
self.email = emailmixin добавляет функциональность JSON любому class, который inherits от него:
user = User("Alice", "alice@example.com")
print(user.to_json())Вывод:
{"name": "Alice", "email": "alice@example.com"}Ключевая идея: теперь у класса User есть метод to_json(), хотя он не определён непосредственно в нём. mixin «подмешал» эту functionality!
Создайте несколько mixin для разной функциональности:
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__Каждый mixin обращается к self.__dict__, чтобы работать с attributes объекта, независимо от того, какой class использует mixin. В этом и заключается сила mixins — они предоставляют повторно используемую functionality, которая работает с attributes любого class.
Объедините несколько mixin в одном классе:
class Product(JSONSerializableMixin, PrintableMixin, ComparableMixin):
def __init__(self, name, price):
self.name = name
self.price = price
product1 = Product("Laptop", 999)
product2 = Product("Laptop", 999)Используйте все функциональные возможности mixin:
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"
- Могут комбинироваться с множественным inheritance
- Работают с
self.__dict__или другими распространёнными возможностями объектов, обеспечивая гибкость
Ключевой момент: миксины предоставляют способ совместно использовать functionality в разных иерархиях class, не создавая сложных деревьев inheritance. Они позволяют «добавлять» определённые возможности, такие как сериализация, сравнение или вывод, в любой class, которому они нужны. Это способствует повторному использованию кода и помогает classes сосредоточиться на своих основных обязанностях.
Задание
СреднеВ этом задании вы реализуете простую систему электронной коммерции с использованием mixins и наследования.
Реализуйте необходимые классы в этих файлах (следуйте комментариям TODO в каждом файле):
printablemixin.py— создайте PrintableMixin с functionality форматированного выводаdiscountmixin.py— реализуйте DiscountMixin для вычисления снижения ценыshippablemixin.py— создайте ShippableMixin для работы с весом и стоимостью доставкиproduct.py— разработайте базовый класс Product с соответствующим наследованием от mixinsphysicalproduct.py— создайте класс PhysicalProduct, расширяющий Productdigitalproduct.py— реализуйте класс DigitalProduct с особым behavior скидки
Попробуйте сами
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():
# Test basic functionality of all classes
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():
# Test method overriding behavior
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():
# Test attribute access patterns
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
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy5Полиморфизм
Снова о переопределении методовУтиная типизацияАбстрактные классы и методыПроектирование интерфейсовИтоги — Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияМиксиныСтатические методы и методы классаДекораторы классовМенеджеры контекста11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Свойства классов
Переменные экземпляра и классаДекораторы свойствПриватные атрибутыИтоги — Менеджер банковского счета6Инкапсуляция
Public, Protected, Private члены классаМодификаторы доступаСокрытие данныхДекораторы property: продвинутый уровеньИтоги: Система учета студентов12Проект: Управление библиотекой
Обзор проектаКлассы Book и UserСистема выдачи книгФункционал поискаИнтерфейс администратораТестирование и интеграцияПотренируйтесь самостоятельно: Онлайн-компилятор Python