Mixins
Teil des Abschnitts Object Oriented Programming der Python-Journey von Coddy — Lektion 38 von 64.
Mixins sind eine spezielle Art der Mehrfachvererbung, die verwendet wird, um zusätzliche Funktionalität in Klassen „einzumischen“. Sie stellen spezifische Methoden bereit, ohne selbst vollständige Klassen zu sein.
Hier ist ein Beispiel für ein einfaches Mixin:
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)Lassen Sie uns aufschlüsseln, was dieser Mixin bewirkt:
self.__dict__– Dieses spezielle Attribut enthält ein Dictionary aller Attribute des Objekts und deren Wertejson.dumps()– Diese Funktion konvertiert das Python-Dictionary in einen JSON-formatierten String- Der Mixin „mischt“ diese JSON-Serialisierungsfunktionalität in jede Klasse ein, die von ihm erbt
Sehen wir es uns nun in Aktion an:
class User(JSONSerializableMixin):
def __init__(self, name, email):
self.name = name
self.email = emailDas Mixin fügt jeder Klasse, die von ihm erbt, JSON-Funktionalität hinzu:
user = User("Alice", "alice@example.com")
print(user.to_json())Ausgabe:
{"name": "Alice", "email": "alice@example.com"}Die entscheidende Erkenntnis: Die User-Klasse verfügt nun über die to_json()-Methode, ohne sie direkt zu definieren. Das Mixin hat diese Funktionalität „eingemischt“!
Erstellen Sie mehrere Mixins für unterschiedliche Funktionalitäten:
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__Jedes Mixin greift auf self.__dict__ zu, um mit den Attributen des Objekts zu arbeiten, unabhängig davon, welche Klasse das Mixin verwendet. Das ist die Stärke von Mixins – sie bieten wiederverwendbare Funktionalität, die mit den Attributen jeder Klasse funktioniert.
Kombinieren Sie mehrere Mixins in einer Klasse:
class Product(JSONSerializableMixin, PrintableMixin, ComparableMixin):
def __init__(self, name, price):
self.name = name
self.price = price
product1 = Product("Laptop", 999)
product2 = Product("Laptop", 999)Nutzen Sie alle Mixin-Funktionalitäten:
print(product1.to_json()) # Von JSONSerializableMixin
product1.pretty_print() # Von PrintableMixin
print(product1 == product2) # Von ComparableMixinAusgabe:
{"name": "Laptop", "price": 999}
name: Laptop
price: 999
TrueHauptmerkmale von Mixins:
- Nicht dazu gedacht, eigenständig instanziiert zu werden
- Bieten spezifische, wiederverwendbare Funktionalität
- Haben normalerweise keine
__init__-Methoden - Namen enden oft auf „Mixin“ oder „able“
- Können mit Mehrfachvererbung kombiniert werden
- Arbeiten mit
self.__dict__oder anderen gängigen Objektmerkmalen zusammen, um flexibel zu sein
Kernpunkt: Mixins bieten eine Möglichkeit, Funktionalität über verschiedene Klassenhierarchien hinweg zu teilen, ohne komplexe Vererbungsbäume zu erstellen. Sie ermöglichen es Ihnen, spezifische Fähigkeiten wie Serialisierung, Vergleich oder Drucken in jede Klasse „einzumischen“, die diese benötigt. Dies fördert die Wiederverwendung von Code und sorgt dafür, dass sich Klassen auf ihre Hauptaufgaben konzentrieren.
Aufgabe
MittelIn dieser Herausforderung implementieren Sie ein einfaches E-Commerce-System unter Verwendung von Mixins und Vererbung.
Implementieren Sie die erforderlichen Klassen in diesen Dateien (folgen Sie den TODO-Kommentaren in jeder Datei):
printablemixin.py- Erstellen Sie das PrintableMixin mit Funktionalität für formatierte Ausgabendiscountmixin.py- Implementieren Sie das DiscountMixin für Preisnachlassberechnungenshippablemixin.py- Erstellen Sie das ShippableMixin für Gewichts- und Versandkostenfunktionenproduct.py- Entwickeln Sie die Basisklasse Product mit entsprechender Mixin-Vererbungphysicalproduct.py- Erstellen Sie die Klasse PhysicalProduct, die Product erweitertdigitalproduct.py- Implementieren Sie die Klasse DigitalProduct mit speziellem Rabattverhalten
Spickzettel
Mixins sind eine spezielle Art der Mehrfachvererbung, die verwendet wird, um zusätzliche Funktionalität in Klassen "einzumischen", ohne dass sie selbst vollständige Klassen sind.
Einfaches Mixin-Beispiel:
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 = emailMehrere Mixins können kombiniert werden:
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 = priceHauptmerkmale von Mixins:
- Nicht dazu gedacht, eigenständig instanziiert zu werden
- Bieten spezifische, wiederverwendbare Funktionalität
- Haben normalerweise keine
__init__-Methoden - Namen enden oft auf "Mixin" oder "able"
- Können mit Mehrfachvererbung kombiniert werden
Probier es selbst
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():
# Teste die Basisfunktionalität aller Klassen
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():
# Teste Grenzfälle wie Null und negative Werte
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():
# Teste mit sehr großen Werten
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():
# Teste Vererbungsbeziehungen
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():
# Teste das Verhalten beim Überschreiben von Methoden
p = Product("Regular Product", 100)
digital = DigitalProduct("Digital Product", 100)
# Gleicher Preis, gleicher Rabattprozentsatz, unterschiedliche Ergebnisse
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)}"
# Digitales Produkt sollte immer 10% Rabatt anwenden, unabhängig vom Parameter
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():
# Teste polymorphes Verhalten mit einer Liste verschiedener Produkttypen
products = [
Product("Regular", 100),
PhysicalProduct("Physical", 100),
DigitalProduct("Digital", 100)
]
# Gewicht für das physische Produkt setzen
products[1].set_weight(10)
# Erwartete Ergebnisse für apply_discount(20)
expected_discounts = [80, 80, 90]
for i, product in enumerate(products):
# Alle sollten die Methode print_details haben
assert "Product:" in product.print_details(), f"Polymorphic print_details failed for {type(product)}"
# Alle sollten die Methode apply_discount haben, aber mit unterschiedlichen Implementierungen
assert product.apply_discount(20) == expected_discounts[i], f"Polymorphic apply_discount failed for {type(product)}"
print("Polymorphism test passed!")
def test_attribute_access():
# Teste Attributzugriffsmuster
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"
# Das Attribut weight sollte nicht existieren, bevor set_weight aufgerufen wird
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!")
# Haupt-Test-Runner
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()Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Object Oriented Programming
1Grundlagen der OOP
Externe DateienEinführung in die OOPKlassen vs. ObjekteDer self-ParameterMethodenAttributeKonstruktor-Methode (__init__)Zusammenfassung – Einfacher Taschenrechner4Vererbung
Grundlagen der VererbungDie super()-FunktionMethoden überschreibenMehrfachvererbungMethod Resolution OrderZusammenfassung - Mitarbeiter-Hierarchie7Spezielle Methoden
Einführung in Magic MethodsOperator-ÜberladungMagic Methods für ContainerRückblick - Eigene Liste10Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterObserver-MusterStrategy-Muster13Abschlussherausforderungen
E-Learning-PlattformBanksystemSpielcharakter-EntwicklungFahrzeugvermietung2Dekoratoren
Einführung in DekoratorenProperty-DekoratorStatischer Methoden-DekoratorKlassenmethoden-Dekorator5Polymorphismus
Methoden-Überschreiben vertieftDuck TypingAbstrakte Klassen und MethodenInterface-DesignZusammenfassung – Formen-Rechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixinsStatische und KlassenmethodenKlassendekoratorenContext Manager3Klasseneigenschaften
Instanz- vs. KlassenvariablenProperty-DekoratorenPrivate AttributeZusammenfassung – Bankkonto-Manager6Kapselung
Public, Protected, Private MemberZugriffsmodifikatorenInformation HidingProperty-Decorators für FortgeschritteneZusammenfassung - Studentenverwaltungssystem12Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen