web-dev-qa-db-fra.com

Python mixins est-il un anti-pattern?

Je suis parfaitement conscient que pylint et d'autres outils d'analyse statique ne sont pas omniscients, et parfois leurs conseils doivent être désobéis. (Cela s'applique à différentes classes de messages, pas seulement à conventions.)


Si j'ai des cours comme

class related_methods():

    def a_method(self):
        self.stack.function(self.my_var)

class more_methods():

    def b_method(self):
        self.otherfunc()

class implement_methods(related_methods, more_methods):

    def __init__(self):
        self.stack  = some()
        self.my_var = other()

    def otherfunc(self):
        self.a_method()

De toute évidence, c'est artificiel. Voici un meilleur exemple, si vous le souhaitez .

Je crois que ce style s'appelle en utilisant des "mixins".

Comme les autres outils, pylint évalue ce code à -21.67 / 10, principalement parce qu'il pense que more_methods et related_methods n'ont pas self ou attributs otherfunc, stack, annd my_var parce que sans exécuter le code, il ne peut apparemment pas voir related_methods et more_methods sont mélangés à implement_methods.

Les compilateurs et les outils d'analyse statique ne peuvent pas toujours résoudre le problème de l'arrêt , mais je pense que c'est certainement un cas dans lequel regarder ce qui est hérité par implement_methods montrerait que c'est parfaitement valable, et ce serait une chose très facile à faire.

Pourquoi les outils d'analyse statique rejettent-ils ce modèle valide (je pense) OOP?

Soit:

  1. Ils n'essaient même pas d'essayer de vérifier l'héritage ou

  2. les mixins sont déconseillés en Python idiomatique et lisible


# 1 est évidemment incorrect parce que si je demande à pylint de me parler d'une de mes classes qui hérite de unittest.TestCase qui utilise self.assertEqual, (quelque chose défini uniquement dans unittest.TestCase), il ne se plaint pas .

Les mixins sont-ils impythoniques ou découragés?

39
cat

Les mixins ne sont tout simplement pas un cas d'utilisation pris en compte par l'outil. Cela ne signifie pas que c'est nécessairement un mauvais cas d'utilisation, juste un cas rare pour python.

Que les mixins soient utilisés de manière appropriée dans un cas particulier est une autre question. L'anti-pattern mixin que je vois le plus souvent est d'utiliser des mixins quand il n'y a jamais eu l'intention de mélanger une seule combinaison. C'est juste un moyen détourné de cacher une classe divine. Si vous ne pouvez pas penser à une raison en ce moment pour échanger ou laisser de côté l'un des mixins, ce ne devrait pas être un mixin.

18
Karl Bielefeldt

Je pense que les Mixins peuvent être absolument Pythoniques. Cependant, la manière idiomatique de faire taire votre linter - et d'améliorer la lisibilité de vos Mixins - est à la fois (1) de définir méthodes abstraites qui définissent explicitement les méthodes que les enfants d'un Mixin doivent implémenter, et à (2) prédéfinir None - des champs valorisés pour les membres de données Mixin que les enfants doivent initialiser.

Appliquer ce modèle à votre exemple:

from abc import ABC, abstractmethod


class related_methods():
    my_var = None
    stack = None

    def a_method(self):
        self.stack.function(self.my_var)


class more_methods(ABC):
    @abstractmethod
    def otherfunc(self):
        pass

    def b_method(self):
        self.otherfunc()


class implement_methods(related_methods, more_methods):
    def __init__(self):
        self.stack  = some()
        self.my_var = other()

    def otherfunc(self):
        self.a_method()
17
UltraBird

je pense les mixins peuvent être bons, mais je pense aussi que le pylint a raison dans ce cas. Avertissement: des informations basées sur les opinions suivent.

Une bonne classe, mixins inclus, a une responsabilité claire. Idéalement, un mixage devrait porter tout l'état auquel il va accéder et la logique pour le gérer. Par exemple. un bon mixin pourrait ajouter un last_updated champ vers une classe de modèle ORM, fournit une logique pour la définir et des méthodes pour rechercher l'enregistrement le plus ancien/le plus récent.

Faire référence à des membres d'instance non déclarés (variables et méthodes) semble un peu bizarre.

La bonne approche dépend beaucoup de la tâche à accomplir.

Il peut s'agir d'un mélange avec le bit d'état pertinent qui y est conservé.

Il peut s'agir d'une hiérarchie de classes différente où les méthodes que vous distribuez actuellement via un mixin sont dans une classe de base, tandis que les différences d'implémentation de niveau inférieur appartiennent à des sous-classes. Cela semble plus adapté à votre cas avec les opérations de pile.

Il peut s'agir d'un décorateur de classe qui ajoute une ou deux méthodes; cela a généralement du sens lorsque vous devez passer des arguments au décorateur pour affecter la génération de la méthode.

Énoncez votre problème, expliquez vos plus grandes préoccupations de conception, nous pourrions alors discuter si quelque chose est un anti-modèle dans votre cas.

9
9000

Le linter ne sait pas que vous utilisez une classe comme mixage. Pylint sait que vous utilisez un mixin si vous ajoutez le suffixe "mixin" ou "Mixin" à la fin du nom de la classe, le linter cesse de se plaindre.

linter_without_mixinslinter2_with_mixin

Les mixins ne sont pas mauvais ou bons en soi, ne sont qu'un outil. Vous en faites un bon ou un mauvais usage.

8
dotoscat

Les mixins sont-ils ok?

Python mixins est-il un anti-pattern?

Les mixins ne sont pas découragés - ils sont un bon cas d'utilisation pour l'héritage multiple.

Pourquoi votre linter se plaint-il?

Pylint se plaint évidemment parce qu'il ne sait pas où les otherfunc, stack et my_var vient de.

Banal?

Il n'y a pas de bonne raison apparente immédiate pour séparer ces deux méthodes en classes parent distinctes, soit dans votre exemple de la question, soit dans votre exemple lié plus trivial, illustré ici.

201
202 class OpCore():
203     # nothing runs without these
204
205 class OpLogik(): 
206     # logic methods... 
207 
208 class OpString(): 
209     # string things
210 
211 
212 class Stack(OpCore, OpLogik, OpString): 
213 
214     "the mixin mixer of the above mixins" 

Coûts des mixins et du bruit

Le coût de ce que vous faites fait du bruit dans votre linter. Ce bruit peut masquer des problèmes plus importants avec votre code. C'est un coût important à peser. Un autre coût est que vous séparez votre code interconnecté en différents espaces de noms qui peuvent rendre plus difficile pour les programmeurs de découvrir la signification de.

Conclusion

L'héritage permet la réutilisation du code. Si vous obtenez la réutilisation du code avec vos mixins, tant mieux, ils ont créé une valeur pour vous qui dépasse probablement les autres coûts possibles. Si vous n'obtenez pas de réutilisation/déduplication de code de lignes de code, vous n'obtenez probablement pas beaucoup de valeur pour vos mixins, et à ce stade, je pense que le coût du bruit est supérieur aux avantages.

1
Aaron Hall