Mixin
Fa parte della sezione Programmazione orientata agli oggetti del percorso Python di Coddy. Lezione 38 di 64.
I mixin sono un tipo speciale di ereditarietà multipla, usato per «aggiungere» funzionalità alle classi. Forniscono metodi specifici senza essere classi complete.
Ecco un esempio di un mixin semplice:
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)Analizziamo nel dettaglio cosa fa questo mixin:
self.__dict__- Questo attributo speciale contiene un dizionario di tutti gli attributi dell'oggetto e dei relativi valorijson.dumps()- Questa funzione converte il dizionario Python in una stringa in formato JSON- Il mixin «aggiunge» questa funzionalità di serializzazione JSON a qualsiasi classe che lo eredita
Ora vediamolo in azione:
class User(JSONSerializableMixin):
def __init__(self, name, email):
self.name = name
self.email = emailIl mixin aggiunge funzionalità JSON a qualsiasi classe che lo eredita:
user = User("Alice", "alice@example.com")
print(user.to_json())Output:
{"name": "Alice", "email": "alice@example.com"}L’intuizione chiave: la classe User ora ha il metodo to_json() senza definirlo direttamente. Il mixin ha «mescolato» questa funzionalità!
Crea più mixin per funzionalità diverse:
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__Ogni mixin accede a self.__dict__ per lavorare con gli attributi dell'oggetto, indipendentemente dalla classe che usa il mixin. Questa è la potenza dei mixin: forniscono funzionalità riutilizzabili che funzionano con gli attributi di qualsiasi classe.
Combina più mixin in una sola classe:
class Product(JSONSerializableMixin, PrintableMixin, ComparableMixin):
def __init__(self, name, price):
self.name = name
self.price = price
product1 = Product("Laptop", 999)
product2 = Product("Laptop", 999)Usa tutte le funzionalità dei mixin:
print(product1.to_json()) # Da JSONSerializableMixin
product1.pretty_print() # Da PrintableMixin
print(product1 == product2) # Da ComparableMixinOutput:
{"name": "Laptop", "price": 999}
name: Laptop
price: 999
TrueCaratteristiche principali dei mixin:
- Non sono pensati per essere istanziati autonomamente
- Forniscono funzionalità specifiche e riutilizzabili
- Di solito non hanno metodi
__init__ - I nomi spesso terminano con "Mixin" o "able"
- Possono essere combinati con l'ereditarietà multipla
- Lavorano con
self.__dict__o altre caratteristiche comuni degli oggetti per essere flessibili
Punto chiave: i mixin offrono un modo per condividere funzionalità tra gerarchie di classi diverse senza creare alberi di ereditarietà complessi. Ti consentono di «includere» capacità specifiche, come la serializzazione, il confronto o la stampa, in qualsiasi classe ne abbia bisogno. Questo favorisce il riutilizzo del codice e mantiene le classi concentrate sulle loro responsabilità principali.
Sfida
MedioIn questa sfida, implementerai un semplice sistema di e-commerce usando mixin ed ereditarietà.
Implementa le classi richieste in questi file (segui i commenti TODO in ogni file):
printablemixin.py- Crea PrintableMixin con funzionalità di output formattatodiscountmixin.py- Implementa DiscountMixin per il calcolo delle riduzioni di prezzoshippablemixin.py- Crea ShippableMixin per le funzionalità relative al peso e ai costi di spedizioneproduct.py- Sviluppa la classe base Product con l'ereditarietà appropriata dei mixinphysicalproduct.py- Crea la classe PhysicalProduct che estende Productdigitalproduct.py- Implementa la classe DigitalProduct con un comportamento speciale per gli sconti
Provalo tu
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():
# Verifica la funzionalità di base di tutte le classi
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():
# Verifica i casi limite, come zero e valori negativi
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():
# Verifica il comportamento con valori molto grandi
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():
# Verifica le relazioni di ereditarietà
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():
# Verifica il comportamento della ridefinizione dei metodi
p = Product("Regular Product", 100)
digital = DigitalProduct("Digital Product", 100)
# Stesso prezzo, stessa percentuale di sconto, risultati diversi
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)}"
# Il prodotto digitale deve sempre applicare uno sconto del 10%, indipendentemente dal parametro
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():
# Verifica il comportamento polimorfico con un elenco di diversi tipi di prodotti
products = [
Product("Regular", 100),
PhysicalProduct("Physical", 100),
DigitalProduct("Digital", 100)
]
# Imposta il peso del prodotto fisico
products[1].set_weight(10)
# Risultati attesi per apply_discount(20)
expected_discounts = [80, 80, 90]
for i, product in enumerate(products):
# Tutti devono avere il metodo print_details
assert "Product:" in product.print_details(), f"Polymorphic print_details failed for {type(product)}"
# Tutti devono avere il metodo apply_discount, ma con implementazioni diverse
assert product.apply_discount(20) == expected_discounts[i], f"Polymorphic apply_discount failed for {type(product)}"
print("Polymorphism test passed!")
def test_attribute_access():
# Verifica le modalità di accesso agli attributi
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"
# L'attributo weight non dovrebbe esistere prima che venga chiamato 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!")
# Programma principale per l'esecuzione dei test
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()Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti di OOP
File esterniIntroduzione a OOPClassi vs. oggettiIl parametro selfMetodiAttributiMetodo costruttore (__init__)Riepilogo - Calcolatrice semplice4Ereditarietà
Ereditarietà di baseLa funzione super()Sovrascrittura dei metodiEreditarietà multiplaOrdine di risoluzione dei metodiRiepilogo - Gerarchia dei dipendenti7Metodi speciali
Introduzione ai metodi magiciSovraccarico degli operatoriMetodi magici dei contenitoriRiepilogo - Lista personalizzata10Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello ObserverModello Strategy2Decoratori
Introduzione ai decoratoriDecoratore di proprietàDecoratore di metodo staticoDecoratore di metodo di classe5Polimorfismo
Ripasso dell'override dei metodiDuck typingClassi e metodi astrattiProgettazione delle interfacceRiepilogo - Calcolatore di forme8Concetti avanzati di OOP
Composizione vs ereditarietàMixinMetodi statici e di classeDecorator di classeContext manager11Pattern di progettazione - Parte 2
Pattern dei comandiPattern adattatorePattern decoratorePattern metodo templatePattern statoPattern composito3Proprietà delle classi
Variabili di istanza e di classeDecorator delle proprietàAttributi privatiRiepilogo - Gestore di conti bancari6Incapsulamento
Membri pubblici, protetti e privatiModificatori di accessoOccultamento delle informazioniDecorator delle proprietà: avanzatiRipasso - Sistema di registri degli studenti12Progetto: Gestione della biblioteca
Panoramica del progettoClassi Libro e UtenteSistema di prestitoFunzionalità di ricercaInterfaccia amministratoreTest e integrazioneEsercitati da solo: Compilatore Python online