En python, un mixin est une classe qui n'as pas vocation à être utilisée seule, mais dans un ensemble d'héritage pour construire une classe plus complète.
Un mixin est souvent utilisé pour concevoir un ensemble de méthodes dont l'usage est proche ou qui partage le même theme ou qui offre une fonctionnalité utilisés conjointement.
Les mixins ansi créés sont utilisés les uns avec les autres pour créer des classes aux fonctionalités riches sans que pour autant le code de chacune ne soit réécrit plusieurs fois.
Django, par exemple, fait grand usage des mixins au seins des GenericViews. Ici un mixin "SingleObjectMixin", là un "MultipleObjectMixin" ou encore un "TemplateRenderMixin"
Ici pas de questions particulière à se poser quand il s'agit de comprendre comment les mixins s'articulent entre eux. Charcun est responsable d'une tache simple et claire, et reprenant la définition du sigle DRY, chaque fonctionnalités est à un seul endroit clairement identifiable.
C'est même diablement pratique et on finis par coder en allant faire son marché. Ici un FormMixin avec un MultipleObjectMixin et votre objet estpratiquement terminé. D'ailleur il n'est pas rare de voir des classes comme ceci
class MonObjet(UnMixin, UnAutreMixin):
passÇa va pas trop dur les mixins ? ;)
Donc, oui les mixins c'est génial, oui il faut en utiliser partout ou c'est nécessaire et pourtant…
Comme toutes les bonnes choses, il ne faut pas en abuser. Particulièrement, il y as deux pièges selon moi auquels il faut faire très attention:
Pour garder un code lisible, on dis souvent qu'il ne faut pas plus de 20 méthodes par objets.C'est arbitraire bien entendu mais à partir de, disons, 50 vous avez vraiment un soucis. Votre classe fait trop de choses et elle n'est plus lisible.
Si pour règler le problème vous faites 10 Mixins et une classe qui hérite de toutes, vous avez un plus grand problème encore.
Vous vous souvenez de :
from malib import *
Et vous savez que c'est mal? C'est mal parceque vous avez perdu les namespace de votre lib et vous vous exposez à l'écrasement involontaire d'une fonction, variable ou classe.
Quand vous héritez d'un ensemble de mixins, vous vous exposez au même risque. Qu'est ce qui vous dis que sur les 10 mixins vous n'aurez pas deux méthodes qui portent le même nom ? Comment allez vous débbuger quand l'une des méthodes va générer une erreur ? Comment retrouver le mixin qui définis cette méthode ?
Une façon simple d'héviter ce soucis est de mettre la logique dans une classe à part et d'en faire un attribut de votre classe principale.
par exemple:
class MaClasse(object):
def __init__(self, *args, **kwargs)
self.api = MonApi()
self.parser = MonParser()
...De cette façon, quand vous accedez aux méthodes de self.api, vous le faites de façon explicite. Comme vous accedez explicitement à la classe MonApi, vous ne pouvez plus écraser par erreur une méthode et vous rendez le debug bien plus simple.
Mais il y a pire que les multiples mixins, il y as aussi les mixins qui ne peuvent exister par elle même. Ça c'est vraiment de la saloperie de haut niveau. Comme vous allez le voir.
La concept est simple, dans l'une de vos mixin, vous appelez une méthode qui est définie dans une autre mixin.
D'une part, si vos deux méthodes sont liées à ce point, l'une appelant l'autre, pourquoi ne pas les avoir écrites dans la même mixin ?
D'autre part, si vous avez fais cela parceque cette autre méthode est définie dans plusieurs autre mixin, vous n'avez aucune sorte de pitié pour celui ou celle qui devra débugger votre code. Jouer avec le MRO n'est jamais une bonne idée
Le MRO c'est le Method Resolution Order. Dans une classe qui hérite de plusieurs autres, il faut lire les méthodes de classe de la gauche vers la droite.
Quand les classes dont vous héritez héritent elles même d'autre classe, vous devez continuer de lire de gauche à droite mais à niveau égal. Un peu perdu? C'est normal. Essayons de clarifier les choses avec un example:
>>> O = object
>>> class F(O): pass
>>> class E(O): pass
>>> class D(O): pass
>>> class C(D,F): pass
>>> class B(E,D): pass
>>> class A(B,C): passA première vue, seriez vous capable de me donner le MRO de la classe A ?
On commence par la gauche. Donc A, puis B puis E puis C puis D puis F puis O
un petit schema ?
---
Level 3 | O |
/ --- \
/ | \
/ | \
/ | \
--- --- ---
Level 2 2 | E | 4 | D | | F | 5
--- --- ---
\ / \ /
\ / \ /
\ / \ /
--- ---
Level 1 1 | B | | C | 3
--- ---
\ /
\ /
---
Level 0 0 | A |
---merci http://www.python.org/download/releases/2.3/mro/ pour cet example.
Que faire quand le sac de noeud est déjà là ? Sois que vous prenniez un code existant, soit que vous soyez développeur Plone ?
Un outils peux venir a votre rescousse : pyreverse.
Pyreverse est fournis avec pylint qui est un outils de test qualité du code.
pip install pylint
Pyreverse va créer pour vous des cartes .dot de votre code que vous allez pouvoir convertir dans le format de votre choix pour les consulter. Par exemple, voici la carte des classes du module views de Django:
pour l'utiliser:
pyreverse votre_module
puis, pour le convertir, en svg par exemple:
dot lefichier.dot -Tsvg -o sortie.svg
Vous pouvez également, et là c'est vraiment très pratique, voir de manière graphique les ancetres d'une classe.
pyreverse votre_module.py -c VotreClass -f ALL
l'option -f ALL vous permet de récupérer toutes les méthodes des parents, y compris les méthodes privées.
En voici un exemple avec la class CreateView des django.genericviews: