Mixins
Fait partie de la section Object Oriented Programming du Journey Python de Coddy — leçon 38 sur 64.
Les mixins sont un type spécial d'héritage multiple utilisé pour « mixer » des fonctionnalités supplémentaires aux classes. Ils fournissent des méthodes spécifiques sans être des classes complètes en soi.
Voici un exemple de mixin simple :
class JSONSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)Analysons ce que fait ce mixin :
self.__dict__- Cet attribut spécial contient un dictionnaire de tous les attributs de l'objet et de leurs valeursjson.dumps()- Cette fonction convertit le dictionnaire Python en une chaîne au format JSON- Le mixin « injecte » cette fonctionnalité de sérialisation JSON dans n'importe quelle classe qui en hérite
Voyons maintenant cela en action :
class User(JSONSerializableMixin):
def __init__(self, name, email):
self.name = name
self.email = emailLe mixin ajoute des fonctionnalités JSON à toute classe qui en hérite :
user = User("Alice", "alice@example.com")
print(user.to_json())Sortie :
{"name": "Alice", "email": "alice@example.com"}L'idée clé : la classe User possède désormais la méthode to_json() sans la définir directement. Le mixin a « injecté » cette fonctionnalité !
Créez plusieurs mixins pour différentes fonctionnalités :
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__Chaque mixin accède à self.__dict__ pour manipuler les attributs de l'objet, quelle que soit la classe qui utilise le mixin. C'est là toute la puissance des mixins : ils fournissent une fonctionnalité réutilisable qui fonctionne avec les attributs de n'importe quelle classe.
Combinez plusieurs mixins dans une seule 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)Utilisez toutes les fonctionnalités des mixins :
print(product1.to_json()) # Provient de JSONSerializableMixin
product1.pretty_print() # Provient de PrintableMixin
print(product1 == product2) # Provient de ComparableMixinSortie :
{"name": "Laptop", "price": 999}
name: Laptop
price: 999
TrueCaractéristiques principales des mixins :
- Ne sont pas destinés à être instanciés seuls
- Fournissent des fonctionnalités spécifiques et réutilisables
- N'ont généralement pas de méthodes
__init__ - Les noms se terminent souvent par « Mixin » ou « able »
- Peuvent être combinés avec l'héritage multiple
- Fonctionnent avec
self.__dict__ou d'autres fonctionnalités d'objet communes pour être flexibles
Point clé : Les mixins offrent un moyen de partager des fonctionnalités entre différentes hiérarchies de classes sans créer d'arbres d'héritage complexes. Ils vous permettent d'« intégrer » des capacités spécifiques telles que la sérialisation, la comparaison ou l'impression à n'importe quelle classe qui en a besoin. Cela favorise la réutilisation du code et permet aux classes de rester concentrées sur leurs responsabilités principales.
Défi
MoyenDans ce défi, vous allez implémenter un système d'e-commerce simple en utilisant des mixins et l'héritage.
Implémentez les classes requises dans ces fichiers (suivez les commentaires TODO dans chaque fichier) :
printablemixin.py- Créez le PrintableMixin avec une fonctionnalité de sortie formatéediscountmixin.py- Implémentez le DiscountMixin pour les calculs de réduction de prixshippablemixin.py- Construisez le ShippableMixin pour les fonctionnalités de poids et de frais d'expéditionproduct.py- Développez la classe de base Product avec l'héritage de mixin appropriéphysicalproduct.py- Créez la classe PhysicalProduct étendant Productdigitalproduct.py- Implémentez la classe DigitalProduct avec un comportement de remise spécial
Aide-mémoire
Les mixins sont un type spécial d'héritage multiple utilisé pour « injecter » des fonctionnalités supplémentaires dans des classes sans être des classes complètes elles-mêmes.
Exemple de mixin de base :
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 = emailPlusieurs mixins peuvent être combinés :
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 = priceCaractéristiques clés des mixins :
- Ils ne sont pas destinés à être instanciés seuls
- Ils fournissent des fonctionnalités spécifiques et réutilisables
- Ils n'ont généralement pas de méthodes
__init__ - Leurs noms se terminent souvent par « Mixin » ou « able »
- Ils peuvent être combinés avec l'héritage multiple
Essayez vous-même
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():
# Tester les fonctionnalités de base de toutes les 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():
# Tester les cas limites comme les valeurs nulles et négatives
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():
# Tester avec de très grandes valeurs
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():
# Tester les relations d'héritage
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():
# Tester le comportement de la redéfinition de méthode
p = Product("Regular Product", 100)
digital = DigitalProduct("Digital Product", 100)
# Même prix, même pourcentage de remise, résultats différents
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)}"
# Le produit numérique doit toujours appliquer 10 % de remise quel que soit le paramètre
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():
# Tester le comportement polymorphe avec une liste de différents types de produits
products = [
Product("Regular", 100),
PhysicalProduct("Physical", 100),
DigitalProduct("Digital", 100)
]
# Définir le poids pour le produit physique
products[1].set_weight(10)
# Résultats attendus pour apply_discount(20)
expected_discounts = [80, 80, 90]
for i, product in enumerate(products):
# Tous doivent avoir la méthode print_details
assert "Product:" in product.print_details(), f"Polymorphic print_details failed for {type(product)}"
# Tous doivent avoir la méthode apply_discount mais avec des implémentations différentes
assert product.apply_discount(20) == expected_discounts[i], f"Polymorphic apply_discount failed for {type(product)}"
print("Polymorphism test passed!")
def test_attribute_access():
# Tester les modèles d'accès aux attributs
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'attribut weight ne doit pas exister avant l'appel de 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!")
# Exécuteur de test principal
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()Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Object Oriented Programming
1Les fondamentaux de la POO
Fichiers externesIntroduction à la POOClasses vs ObjetsLe paramètre selfMéthodesAttributsMéthode constructeur (__init__)Récapitulatif - Calculatrice simple4Héritage
Héritage de baseLa fonction super()Redéfinition de méthodeHéritage multipleOrdre de résolution des méthodesRécapitulatif - Hiérarchie des employés7Méthodes spéciales
Introduction aux méthodes magiquesSurcharge d'opérateursMéthodes magiques de conteneurRécapitulatif - Liste personnalisée10Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FactoryPatron ObserverPatron Strategy2Décorateurs
Introduction aux décorateursDécorateur de propriétéDécorateur de méthode statiqueDécorateur de méthode de classe5Polymorphisme
Retour sur la redéfinition de méthodeDuck TypingClasses et méthodes abstraitesConception d'interfacesRécapitulatif - Calculateur de formes8Concepts avancés de la POO
Composition vs HéritageMixinsMéthodes statiques et de classeDécorateurs de classeGestionnaires de contexte11Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Méthode TemplatePatron ÉtatPatron Composite3Propriétés de classe
Variables d'instance vs variables de classeDécorateurs de propriétéAttributs privésRécapitulatif - Gestionnaire de compte bancaire