Les Crash-Devs d'un Passionné

Quel usage pour les mixins

/Catégorie/python

Temps de lecture : 4 minutes

Commençons par rappeler qu'en

python
le multiple héritage est possible. C'est-à-dire qu'une classe peut hériter de plusieurs parents

clipboard
Copier le code
1
2
class Cat(Animal, Mammal):
  pass

Pour déterminer si un lien entre une classe enfant et classe(s) parent(s) est cohérent, on peut utiliser le verbe être.

Le chat est un animal et est un mammifère

En mettant en place de l'héritage, on souhaite lier de manière forte les classes entre elles avec un contrat d'interface. Ces classes parents définissent un comportement et ont une interaction systématique avec l'enfant.

Et le mixin dans tout cela ?

C'est une classe qui va étendre les fonctionnalités de la classe qui hérite de ce mixin. L'exemple que l'on trouve souvent est la sérialisation d'un objet

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class JSONSerializableMixin:
  
    def to_json(self, dict_data) -> str:
        return json.dumps(dict_data)

class User(JSONSerializableMixin):
    ...
    def save():
        with open('file.txt') as f:
              f.write(self.to_json(self.data))

Cette approche permet de découper le rôle de chacun. Évidemment, dans cet exemple la méthode

save()
doit être décorrelée de la classe
User
qui contient les données, mais pour l'exemple restons simple.

Il faut toujours garder en tête les principes de base de la programmation comme S.O.L.I.D. Ici le Single Responsability Principle est bien en place.

On donne plus de responsabilités à User avec la sérialisation de son objet, mais cela ne repose pas sur son fonctionnement en lui-même. En effet, on ne manipule pas son instance (méthode ou attribut de l'enfant), on effectue une transformation. On ne change pas la donnée initiale.

C'est un principe pour un mixin qui est souvent stateless

La relation entre les objets est faible, comme on peut le voir sur cet exemple sur la mise en place de la configuration d'une application FastApi à partir de variables d'environnement

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class NeoModelConfigMixin:
    neo4j_url: str = Field(default="bolt://localhost:7687/neo4j")

class AmqpConfigMixin:
    AMQP_PROTOCOL = r"^(amqps?://).*"

    amqp_url: str = Field(default=None, pattern=AMQP_PROTOCOL)

class Settings(BaseSettings, NeoModelConfigMixin, AmqpConfigMixin):
    model_config = SettingsConfigDict(env_file=find_env_file())

On étend la classe

Settings
avec de la configuration spécifique, ici neo4j et rabbitmq. On enrichit la configuration.

Un mixin n'a pas lieu d'exister seul, il doit forcément être parent de quelqu'un. Car il permet d'acquérir de nouvelles fonctionnalités à l'enfant.

Un mixin est souvent voisin d'un autre mixin, car un mixin doit porter une seule responsabilité (principes S.O.L.I.D.)

Il est important de préciser que l'ordre d'héritage est crucial, de gauche à droite, vous pouvez le connaitre en appelant le MRO. Faites attention quand vous avez beaucoup de mixins à ce que l'un ne surcharge pas l'autre et apporte une erreur silencieuse. Ma préconisation est de limiter à 3 parents (à ne pas généraliser car il dépend du contexte), sinon il faut réfléchir à implémenter un design pattern (Factory, Builder...)

Un autre point important pour le mixin est sa non-substitution, il ne doit pas être covariant. On ne peut pas dire que

Settings
est un
NeoModelConfigMixin.
Contrairement à un lien d'héritage traditionnel où on souhaite faire cela

Mettre en place un mixin évite de généraliser un comportement dans un parent pour un enfant A qui en aurait besoin et pas pour un enfant B.

Un mixin apporte plus de souplesse qu'un héritage classique, il repose sur le fait que python permet de faire du multiple héritage. Son implémentation est bien de l'héritage mais avec une approche plus légère. Il a la capacité à faire quelque chose et non pas d'être quelque chose (héritage classique)

Vous l'avez vu la syntaxe est simple, il suffit de suffixer votre classe par Mixin, python ne suggère pas d'autre structure plus rigoureuse