Question Qu'est-ce qu'un mixin, et pourquoi sont-ils utiles?


Dans "Programmation de Python", Mark Lutz mentionne" mixins ", je viens d'un arrière-plan en C / C ++ / C # et je n'ai pas encore entendu le terme.

Lecture entre les lignes de cet exemple (que j'ai lié à parce que c'est assez long), je suppose qu'il est un cas d'utilisation de l'héritage multiple pour étendre une classe par opposition à la sous-classe «appropriée». Est-ce correct?

Pourquoi voudrais-je faire cela plutôt que de mettre la nouvelle fonctionnalité dans une sous-classe? D'ailleurs, pourquoi une approche mixin / héritage multiple serait-elle préférable à l'utilisation de la composition?

Qu'est-ce qui sépare un mixin de l'héritage multiple? Est-ce juste une question de sémantique?


727
2018-02-10 18:50


origine


Réponses:


Un mixin est un type spécial d'héritage multiple. Il y a deux situations principales où les mixines sont utilisées:

  1. Vous voulez fournir beaucoup de fonctionnalités optionnelles pour une classe.
  2. Vous voulez utiliser une fonctionnalité particulière dans un grand nombre de classes différentes.

Pour un exemple de numéro un, considérez Système de requête et de réponse de werkzeug. Je peux créer un objet de requête simple en disant:

from werkzeug import BaseRequest

class Request(BaseRequest):
    pass

Si je veux ajouter accepter le support de l'en-tête, je le ferais

from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
    pass

Si je voulais créer un objet de requête prenant en charge les en-têtes, les etags, l'authentification et la prise en charge de l'agent utilisateur, je pouvais le faire:

from werkzeug import BaseRequest, AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin

class Request(AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin, BaseRequest):
    pass

La différence est subtile, mais dans les exemples ci-dessus, les classes de mixage n'ont pas été conçues pour se suffire à elles-mêmes. Dans l'héritage multiple plus traditionnel, le AuthenticationMixin (par exemple) serait probablement quelque chose de plus Authenticator. Autrement dit, la classe serait probablement conçue pour se suffire à elle-même.


536
2018-02-13 21:15



Tout d'abord, vous devez noter que les mixins existent uniquement dans les langages à plusieurs héritages. Vous ne pouvez pas faire un mixin en Java ou en C #.

Fondamentalement, un mixin est un type de base autonome qui offre une fonctionnalité limitée et une résonance polymorphe pour une classe enfant. Si vous pensez en C #, pensez à une interface que vous n'avez pas à implémenter car elle est déjà implémentée; vous en héritez et profitez de ses fonctionnalités.

Les mixines ont généralement une portée étroite et ne sont pas destinées à être étendues.

[edit - pourquoi?]

Je suppose que je devrais expliquer pourquoi, puisque vous avez demandé. Le gros avantage est que vous n'avez pas à le faire vous-même encore et encore. En C #, le plus grand endroit où un mixin pourrait bénéficier pourrait être du Modèle d'élimination. Chaque fois que vous implémentez IDisposable, vous voulez presque toujours suivre le même modèle, mais vous finissez par écrire et réécrire le même code de base avec des variations mineures. S'il y avait un mixage Disposal extensible, vous pourriez vous épargner beaucoup de frappe supplémentaire.

[edit 2 - pour répondre à vos autres questions]

Qu'est-ce qui sépare un mixin de l'héritage multiple? Est-ce juste une question de sémantique?

Oui. La différence entre un mélange et un héritage multiple standard est juste une question de sémantique; une classe qui a plusieurs héritages peut utiliser un mix dans le cadre de cet héritage multiple.

Le but d'un mixin est de créer un type qui peut être "mélangé" à tout autre type via l'héritage sans affecter le type d'héritage tout en offrant des fonctionnalités bénéfiques pour ce type.

Encore une fois, pensez à une interface déjà implémentée.

Personnellement, je n'utilise pas de mixins, car je me développe principalement dans un langage qui ne les supporte pas, donc j'ai du mal à trouver un exemple décent qui suffira à fournir ce "ahah!" moment pour vous. Mais je vais essayer à nouveau. Je vais utiliser un exemple qui est artificiel - la plupart des langages fournissent déjà cette fonctionnalité d'une manière ou d'une autre - mais j'espère que cela expliquera comment les mixins sont supposés être créés et utilisés. Voici:

Supposons que vous ayez un type que vous voulez pouvoir sérialiser vers et depuis XML. Vous voulez que le type fournisse une méthode "ToXML" qui renvoie une chaîne contenant un fragment XML avec les valeurs de données du type, et un "FromXML" qui permette au type de reconstruire ses valeurs de données à partir d'un fragment XML dans une chaîne. Encore une fois, il s'agit d'un exemple inventé, donc peut-être que vous utilisez un flux de fichier, ou une classe XML Writer de la bibliothèque d'exécution de votre langue ... peu importe. Le point est que vous voulez sérialiser votre objet en XML et récupérer un nouvel objet à partir de XML.

L'autre point important dans cet exemple est que vous voulez le faire de manière générique. Vous ne voulez pas avoir à implémenter une méthode "ToXML" et "FromXML" pour chaque type que vous voulez sérialiser, vous voulez des moyens génériques pour vous assurer que votre type va le faire et ça fonctionne. Vous voulez la réutilisation de code.

Si votre langue le supporte, vous pouvez créer le mixin XmlSerializable pour faire votre travail pour vous. Ce type implémenterait les méthodes ToXML et FromXML. Il serait possible, en utilisant un mécanisme qui n'est pas important pour l'exemple, de rassembler toutes les données nécessaires à partir de n'importe quel type avec lequel construire le fragment XML retourné par ToXML et il serait également capable de restaurer ces données lorsque FromXML est appelé.

Et c'est tout. Pour l'utiliser, vous auriez tout type qui doit être sérialisé en XML hérité de XmlSerializable. Chaque fois que vous deviez sérialiser ou désérialiser ce type, vous appeliez simplement ToXML ou FromXML. En fait, étant donné que XmlSerializable est un type à part entière et polymorphe, vous pourriez concevoir un sérialiseur de document qui ne connaît rien à votre type d'origine, n'acceptant, disons, qu'un tableau de types XmlSerializable.

Imaginons maintenant d'utiliser ce scénario pour d'autres choses, comme créer un mixin qui assure que chaque classe qui le mélange enregistre chaque appel de méthode, ou un mixin qui fournit la transactionnalité au type qui le mélange. La liste peut s'allonger encore et encore.

Si vous pensez simplement à un mixin comme un petit type de base conçu pour ajouter une petite quantité de fonctionnalités à un type sans affecter autrement ce type, alors vous êtes en or.

Espérons. :)


195
2018-02-10 19:00



Cette réponse vise à expliquer les mixins avec des exemples qui sont:

  • autonome: bref, sans avoir besoin de connaître les bibliothèques pour comprendre l'exemple.

  • en Python, pas dans d'autres langues.

    Il est compréhensible qu'il y ait eu des exemples d'autres langues telles que Ruby puisque le terme est beaucoup plus commun dans ces langues, mais c'est un Python fil.

Il doit également considérer 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 une citation d'une source "autoritaire" 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 des autres concepts similaires tels que les classes de base abstraites), et les gens ne sont pas entièrement d'accord sur ce 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 lever une exception.

Une contrainte que certaines sources ajoutent est que la classe ne peut pas contenir de données, seulement des méthodes, mais je ne vois pas pourquoi cela est nécessaire. En pratique cependant, beaucoup de mixins utiles n'ont pas de données, et les classes de base sans données sont plus simples à utiliser.

Un exemple classique est la mise en œuvre de tous les opérateurs de comparaison de seulement <= et ==:

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 é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 mixin fait référence aux classes de base qui sont destinées à être utilisées dans ce modèle de conception (TODO ceux qui utilisent la méthode, ou ceux qui l'implémentent?)

Il n'est pas facile de décider si une classe donnée est un mixin ou non: la méthode pourrait simplement être 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 modèle 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é

Au documentatiton officielle pour 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

puis la classe obtient un __iter__  méthode mixin gratuitement.

Donc au moins sur ce point de la documentation, mixin ne nécessite pas d'héritage multipleet 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 les classes aiment Set et Iteratorpeut être appelé 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 Programmation Ruby et le langage de programmation Ruby

  • 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


118
2017-11-16 19:36



Je pense à eux comme une manière disciplinée d'utiliser l'héritage multiple - parce que finalement un mixin est juste une autre classe python qui (pourrait) suivre les conventions sur les classes qui sont appelées mixins.

Ma compréhension des conventions qui régissent quelque chose que vous appelleriez un Mixin est un Mixin:

  • ajoute des méthodes mais pas des variables d'instance (les constantes de classe sont OK)
  • seulement hérite de object (en Python)

De cette façon, il limite la complexité potentielle de l'héritage multiple et facilite le suivi du flux de votre programme en limitant l'endroit où vous devez regarder (par rapport à l'héritage multiple complet). Ils sont similaires aux modules ruby.

Si je veux ajouter des variables d'instance (avec plus de flexibilité que ce qui est autorisé par un héritage simple) alors j'ai tendance à opter pour la composition.

Cela dit, j'ai vu des classes appelées XYZMixin qui ont des variables d'instance.


26
2017-07-05 14:26



Mixins est un concept de programmation dans lequel la classe fournit des fonctionnalités mais il n'est pas destiné à être utilisé pour l'instanciation. Le but principal de Mixins est de fournir des fonctionnalités autonomes et il serait préférable que les mixins eux-mêmes n'aient pas d'héritage avec d'autres mixins et évitent également l'état. Dans des langages tels que Ruby, il existe un support de langage direct mais pour Python, il n'y en a pas. Cependant, vous pouvez utiliser l'héritage multi-classes pour exécuter la fonctionnalité fournie dans Python.

J'ai regardé cette vidéo http://www.youtube.com/watch?v=v_uKI2NOLEM pour comprendre les bases des mixins. Il est très utile pour un débutant de comprendre les bases des mixins et comment ils fonctionnent et les problèmes que vous pourriez rencontrer lors de leur implémentation.

Wikipédia est toujours le meilleur: http://en.wikipedia.org/wiki/Mixin


19
2018-06-13 05:06



Je déconseille les mix-ins dans le nouveau code Python, si vous pouvez trouver un autre moyen de le contourner (comme la composition au lieu de l'héritage, ou juste les méthodes de correction de singe dans vos propres classes) qui n'est pas beaucoup plus effort.

Dans les classes à l'ancienne, vous pouvez utiliser des mix-ins pour saisir quelques méthodes d'une autre classe. Mais dans le monde du nouveau style tout, même le mix-in, hérite de object. Cela signifie que toute utilisation de l'héritage multiple introduit naturellement Problèmes MRO.

Il existe plusieurs façons de faire fonctionner le MRO à héritage multiple en Python, notamment la fonction super (), mais cela signifie que vous devez faire toute la hiérarchie de classe en utilisant super (), et il est beaucoup plus difficile de comprendre le flux de contrôle.


11
2018-02-10 19:21



Qu'est-ce qui sépare un mixin de l'héritage multiple? Est-ce juste une question de sémantique?

Un mixin est une forme limitée d'héritage multiple. Dans certaines langues, le mécanisme d'ajout d'un mixin à une classe est légèrement différent (en termes de syntaxe) de celui de l'héritage.

Dans le contexte de Python en particulier, un mixin est une classe parente qui fournit des fonctionnalités aux sous-classes mais qui n'est pas destinée à être instanciée elle-même.

Ce qui pourrait vous amener à dire, "c'est juste de l'héritage multiple, pas vraiment un mixage", c'est si la classe qui pourrait être confondue pour un mixin peut être instanciée et utilisée - alors en effet c'est une différence sémantique et très réelle.

Exemple d'héritage multiple

Cet exemple, de la documentation, est un OrderedCounter:

class OrderedCounter(Counter, OrderedDict):
     'Counter that remembers the order elements are first encountered'

     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))

     def __reduce__(self):
         return self.__class__, (OrderedDict(self),)

Il sous-classe à la fois Counter et le OrderedDict du collections module.

Tous les deux Counter et OrderedDict sont destinés à être instanciés et utilisés seuls. Cependant, en les sous-classant tous les deux, nous pouvons avoir un compteur qui est ordonné et réutilise le code dans chaque objet.

C'est un moyen puissant de réutiliser le code, mais cela peut aussi être problématique. S'il s'avère qu'il y a un bug dans l'un des objets, le fait de le réparer sans précaution pourrait créer un bug dans la sous-classe.

Exemple de Mixin

Les mixins sont généralement promus comme le moyen de réutiliser le code sans problèmes de couplage potentiels que l'héritage multiple coopératif, comme le OrderedCounter, pourrait avoir. Lorsque vous utilisez des mixins, vous utilisez des fonctionnalités qui ne sont pas aussi étroitement couplées aux données.

Contrairement à l'exemple ci-dessus, un mixin n'est pas destiné à être utilisé seul. Il fournit des fonctionnalités nouvelles ou différentes.

Par exemple, la bibliothèque standard a quelques les mixins dans le socketserver bibliothèque.

Des versions de forking et de threading de chaque type de serveur peuvent être créées   en utilisant ces classes de mix-in. Par exemple, ThreadingUDPServer est   créé comme suit:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

La classe de mélange vient en premier, car elle remplace une méthode définie dans   UDPServer. La définition des différents attributs modifie également le comportement de   le mécanisme du serveur sous-jacent.

Dans ce cas, les méthodes mixin remplacent les méthodes dans le UDPServer définition d'objet pour autoriser la concurrence.

La méthode surchargée semble être process_request et il fournit également une autre méthode, process_request_thread. Ici, c'est de la code source:

class ThreadingMixIn:
        """Mix-in class to handle each request in a new thread."""

        # Decides how threads will act upon termination of the
        # main process
        daemon_threads = False

        def process_request_thread(self, request, client_address):
            """Same as in BaseServer but as a thread.
            In addition, exception handling is done here.
            """
            try:
                self.finish_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
            finally:
                self.shutdown_request(request)

        def process_request(self, request, client_address):
            """Start a new thread to process the request."""
            t = threading.Thread(target = self.process_request_thread,
                                 args = (request, client_address))
            t.daemon = self.daemon_threads
            t.start()

Un exemple contredit

C'est un mixin qui est principalement à des fins de démonstration - la plupart des objets évolueront au-delà de l'utilité de cette repr:

class SimpleInitReprMixin(object):
    """mixin, don't instantiate - useful for classes instantiable
    by keyword arguments to their __init__ method.
    """
    __slots__ = () # allow subclasses to use __slots__ to prevent __dict__
    def __repr__(self):
        kwarg_strings = []
        d = getattr(self, '__dict__', None)
        if d is not None:
            for k, v in d.items():
                kwarg_strings.append('{k}={v}'.format(k=k, v=repr(v)))
        slots = getattr(self, '__slots__', None)
        if slots is not None:
            for k in slots:
                v = getattr(self, k, None)
                kwarg_strings.append('{k}={v}'.format(k=k, v=repr(v)))
        return '{name}({kwargs})'.format(
          name=type(self).__name__,
          kwargs=', '.join(kwarg_strings)
          )

et l'utilisation serait:

class Foo(SimpleInitReprMixin): # add other mixins and/or extend another class here
    __slots__ = 'foo',
    def __init__(self, foo=None):
        self.foo = foo
        super(Foo, self).__init__()

Et utilisation:

>>> f1 = Foo('bar')
>>> f2 = Foo()
>>> f1
Foo(foo='bar')
>>> f2
Foo(foo=None)

11
2018-03-25 15:05



Peut-être que quelques exemples vont aider.

Si vous construisez un cours et que vous voulez qu'il se comporte comme un dictionnaire, vous pouvez définir tous les __ __ méthodes nécessaires. Mais c'est un peu douloureux. Comme alternative, vous pouvez en définir quelques-uns et hériter (en plus de tout autre héritage) de UserDict.DictMixin (déplacé à collections.DictMixin en py3k). Cela aura pour effet de définir automatiquement tout le reste de l'API du dictionnaire.

Un deuxième exemple: la boîte à outils GUI wxPython vous permet de faire des contrôles de liste avec plusieurs colonnes (comme, disons, l'affichage du fichier dans l'Explorateur Windows). Par défaut, ces listes sont assez basiques. Vous pouvez ajouter des fonctionnalités supplémentaires, telles que la possibilité de trier la liste par une colonne particulière en cliquant sur l'en-tête de colonne, en héritant de ListCtrl et en ajoutant des mixins appropriés.


8
2018-02-10 21:27



Je pense qu'il y a eu quelques bonnes explications ici, mais je voulais donner un autre point de vue.

Dans Scala, vous pouvez faire des mixins comme cela a été décrit ici, mais ce qui est très intéressant, c'est que les mixins sont fusionnés pour créer un nouveau type de classe à hériter. En substance, vous n'héritez pas de plusieurs classes / mixins, mais plutôt, générez un nouveau type de classe avec toutes les propriétés du mixin à hériter. Cela a du sens puisque Scala est basé sur la JVM où l'héritage multiple n'est pas actuellement supporté (à partir de Java 8). Ce type de classe mixin, en passant, est un type spécial appelé Trait in Scala.

Il est fait allusion à la façon dont une classe est définie:     classe NewClass étend FirstMixin avec SecondMixin avec ThirdMixin     ...

Je ne sais pas si l'interpréteur CPython fait la même chose (mixin class-composition) mais je ne serais pas surpris. Aussi, venant d'un fond C ++, je n'appellerais pas un ABC ou une 'interface' équivalente à un mixin - c'est un concept similaire mais divergent dans l'utilisation et l'implémentation.


8
2017-12-26 05:54