Cette réponse vise à expliquer les mixins avec des exemples qui sont:
autonome : bref, sans avoir besoin de connaître aucune bibliothèque pour comprendre l'exemple.
en Python , pas dans d'autres langues.
Il est compréhensible qu'il y ait eu des exemples provenant d'autres langages tels que Ruby puisque le terme est beaucoup plus courant dans ces langages, mais il s'agit d'un thread Python .
Il examinera également la question controversée:
L'héritage multiple est-il nécessaire ou non pour caractériser un mixin?
Définitions
Je n'ai pas encore vu de citation d'une source "faisant autorité" disant clairement ce qu'est un mixin en Python.
J'ai vu 2 définitions possibles d'un mixin (si elles doivent être considérées comme différentes d'autres concepts similaires tels que les classes de base abstraites), et les gens ne sont pas entièrement d'accord sur celui qui est correct.
Le consensus peut varier entre différentes langues.
Définition 1: pas d'héritage multiple
Un mixin est une classe telle qu'une méthode de la classe utilise une méthode qui n'est pas définie dans la classe.
Par conséquent, la classe n'est pas destinée à être instanciée, mais plutôt à servir de classe de base. Sinon, l'instance aurait des méthodes qui ne peuvent pas être appelées sans déclencher une exception.
Une contrainte que certaines sources ajoutent est que la classe peut ne pas contenir de données, uniquement des méthodes, mais je ne vois pas pourquoi cela est nécessaire. Cependant, dans la pratique, de nombreux mixins utiles n'ont pas de données et les classes de base sans données sont plus simples à utiliser.
Un exemple classique est l'implémentation de tous les opérateurs de comparaison à partir de <=
et uniquement ==
:
class ComparableMixin(object):
"""This class has methods which use `<=` and `==`,
but this class does NOT implement those methods."""
def __ne__(self, other):
return not (self == other)
def __lt__(self, other):
return self <= other and (self != other)
def __gt__(self, other):
return not self <= other
def __ge__(self, other):
return self == other or self > other
class Integer(ComparableMixin):
def __init__(self, i):
self.i = i
def __le__(self, other):
return self.i <= other.i
def __eq__(self, other):
return self.i == other.i
assert Integer(0) < Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) > Integer(0)
assert Integer(1) >= Integer(1)
# It is possible to instantiate a mixin:
o = ComparableMixin()
# but one of its methods raise an exception:
#o != o
Cet exemple particulier aurait pu être réalisé via le functools.total_ordering()
décorateur, mais le jeu ici était de réinventer la roue:
import functools
@functools.total_ordering
class Integer(object):
def __init__(self, i):
self.i = i
def __le__(self, other):
return self.i <= other.i
def __eq__(self, other):
return self.i == other.i
assert Integer(0) < Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) > Integer(0)
assert Integer(1) >= Integer(1)
Définition 2: héritage multiple
Un mixin est un modèle de conception dans lequel une méthode d'une classe de base utilise une méthode qu'elle ne définit pas, et cette méthode est destinée à être implémentée par une autre classe de base , pas par le dérivé comme dans la définition 1.
Le terme classe de mixage fait référence aux classes de base qui sont destinées à être utilisées dans ce modèle de conception (TODO celles qui utilisent la méthode ou celles qui la mettent en œuvre?)
Il n'est pas facile de décider si une classe donnée est un mixin ou non: la méthode pourrait être simplement implémentée sur la classe dérivée, auquel cas nous revenons à la Définition 1. Vous devez considérer les intentions de l'auteur.
Ce schéma est intéressant car il est possible de recombiner des fonctionnalités avec différents choix de classes de base:
class HasMethod1(object):
def method(self):
return 1
class HasMethod2(object):
def method(self):
return 2
class UsesMethod10(object):
def usesMethod(self):
return self.method() + 10
class UsesMethod20(object):
def usesMethod(self):
return self.method() + 20
class C1_10(HasMethod1, UsesMethod10): pass
class C1_20(HasMethod1, UsesMethod20): pass
class C2_10(HasMethod2, UsesMethod10): pass
class C2_20(HasMethod2, UsesMethod20): pass
assert C1_10().usesMethod() == 11
assert C1_20().usesMethod() == 21
assert C2_10().usesMethod() == 12
assert C2_20().usesMethod() == 22
# Nothing prevents implementing the method
# on the base class like in Definition 1:
class C3_10(UsesMethod10):
def method(self):
return 3
assert C3_10().usesMethod() == 13
Occurrences Python faisant autorité
Dans la documentation officielle de collections.abc, la documentation utilise explicitement le terme Méthodes Mixin .
Il indique que si une classe:
- met en oeuvre
__next__
- hérite d'une seule classe
Iterator
alors la classe obtient gratuitement une __iter__
méthode de mixage .
Par conséquent, au moins sur ce point de la documentation, mixin ne nécessite pas d'héritage multiple et est cohérent avec la définition 1.
La documentation pourrait bien sûr être contradictoire à différents points, et d'autres bibliothèques Python importantes pourraient utiliser l'autre définition dans leur documentation.
Cette page utilise également le terme Set mixin
, ce qui suggère clairement que des classes comme Set
etIterator
peuvent être appelées classes Mixin.
Dans d'autres langues
Ruby: Clairement ne nécessite pas d'héritage multiple pour mixin, comme mentionné dans les principaux livres de référence tels que Programming Ruby et The Ruby Programming Language
C ++: Une méthode qui n'est pas implémentée est une méthode virtuelle pure.
La définition 1 coïncide avec la définition d'une classe abstraite (une classe qui a une méthode virtuelle pure). Cette classe ne peut pas être instanciée.
La définition 2 est possible avec l'héritage virtuel: héritage multiple de deux classes dérivées