Décorateurs de classe en Python: cas d'utilisation pratiques

9

Je recherche des cas d'utilisation pratiques et non synthétiques de décorateurs de classe Python. Jusqu'à présent, le seul cas qui avait du sens pour moi est l'enregistrement d'une classe dans un système éditeur-abonné, par exemple des plugins ou des événements, quelque chose comme:

@register
class MyPlugin(Plugin):
    pass

ou

@recieves_notifications
class Console:
    def print(self, text):
        ...

Tout autre cas sain auquel j'ai pensé aurait pu être construit en plus de l'héritage, des métaclasses ou des méthodes de décoration. Pourriez-vous s'il vous plaît partager de bons (ou mauvais!) Exemples d'utilisation de décorateurs de classe?

Je vous remercie!

Zaur Nasibov
la source
Pour commencer, les décorateurs sont conceptuellement beaucoup plus simples que les métaclasses. Il est beaucoup plus simple d'écrire un décorateur qu'une métaclasse et évite d'avoir à comprendre comment combiner plusieurs métaclasses sans rien casser.
amon
@amon lorsqu'une métaclasse est utilisée par un développeur chevronné, cela signifie généralement que d'autres options ont été soigneusement étudiées et que la métaclasse est jusqu'à présent le meilleur moyen de résoudre le problème. De plus, explicite vaut mieux qu'implicite - c'est peut-être la raison pour laquelle (à titre d'exemple) nous avons ABCMeta, pas @abstractclassdécorateur de classe.
Zaur Nasibov
2
Je suis un programmeur Python, et honnêtement, je n'en vois pas le besoin. Ce n'est pas parce qu'une fonctionnalité linguistique existe que vous devez ou même devez l'utiliser. Les concepteurs proposent toutes sortes de fonctionnalités folles qui se sont avérées inutiles ou même dommageables avec le recul. En fait, vous avez mentionné deux autres fonctionnalités de ce type dans votre question :)
gardenhead
1
Pas quelque chose dont je ne pourrais pas me passer, mais quelque chose que j'ai trouvé utile est d'utiliser un décorateur de classe pour enregistrer toutes les entrées et sorties de toutes les méthodes de la classe.
bgusach

Réponses:

8

Lors de l'écriture de tests unitaires ayant le @unittest.skipIfet @unittest.skipUnlesspeut être utilisé sur des méthodes individuelles ou sur une unittest.TestCasedéfinition de classe entière . Cela facilite grandement l'écriture de tests pour des problèmes spécifiques à la plate-forme.

Exemple de asyncio/test_selectors

# Some platforms don't define the select.kqueue object for these tests.
# On those platforms, this entire grouping of tests will be skipped.

@unittest.skipUnless(hasattr(selectors, 'KqueueSelector'),
                 "Test needs selectors.KqueueSelector)")
class KqueueSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn):
...
sethmlarson
la source
2

Les décorateurs de classe ont été explicitement créés pour rendre plus agréables certaines choses qui étaient déjà exprimables via des métaclasses, du PEP :

Le cas d'utilisation motivant était de rendre certaines constructions plus facilement exprimées et moins dépendantes des détails d'implémentation de l'interpréteur CPython. Bien qu'il soit possible d'exprimer une fonctionnalité de type décorateur de classe à l'aide de métaclasses, les résultats sont généralement désagréables et la mise en œuvre très fragile. En outre, les métaclasses sont héritées, contrairement aux décorateurs de classe, ce qui rend les métaclasses inadaptées à certaines utilisations spécifiques à une classe des décorateurs de classe. Le fait que des projets Python à grande échelle comme Zope traversent ces contorsions sauvages pour réaliser quelque chose comme des décorateurs de classe a conquis le BDFL.

quodlibetor
la source
Merci d'avoir cité PEP-3129. Cependant, la question n'est pas de savoir pourquoi les décorateurs de classe existent, ce que Guido pense d'eux et comment Zope ou IronPython auraient pu les utiliser avant qu'ils n'apparaissent. La question porte sur leur utilisation pratique aujourd'hui, en 2016.
Zaur Nasibov
1
À droite, le fait est que la question d'origine évoque les métaclasses comme quelque chose qui peut être utilisé pour implémenter une fonctionnalité de classe-décorateur, mais l'intérêt des décorateurs est qu'ils sont plus faciles à utiliser et plus évidents que les métaclasses. Donc, la réponse à cette partie de la question est "Si vous êtes partagé entre décorateurs ou métaclasses, vous devriez utiliser des décorateurs car ils sont plus faciles à comprendre pour les autres."
quodlibetor
Vous laissez plus de questions que de réponses. "... tout l'intérêt des décorateurs est qu'ils sont plus faciles à utiliser et plus évidents que les métaclasses" - Dans quels cas? Alors "... vous devriez utiliser des décorateurs car ils sont plus faciles à comprendre pour les autres" Vraiment? Pendant une longue période de dépendance à Python, j'ai vu des tonnes de métaclasses, certaines laides, d'autres belles. Mais je n'ai découvert aucun décorateur de classe.
Zaur Nasibov
1

De cette question de débordement de pile:

def singleton(class_):
  instances = {}
  def getinstance(*args, **kwargs):
    if class_ not in instances:
        instances[class_] = class_(*args, **kwargs)
    return instances[class_]
  return getinstance

@singleton
class MyClass(BaseClass):
  pass
Jared Smith
la source
Oui, mais le contre est donné juste dans la même question SO: la métaclasse est une meilleure approche.
Zaur Nasibov
1

La recherche de cas d'utilisation pouvant être réalisés avec des décorateurs dans les classes est futile - tout ce qui peut être fait avec des décorateurs dans les classes peut être fait avec des métaclasses. Même votre exemple d'inscription. Pour le prouver, voici une métaclasse qui applique un décorateur:

Python 2:

def register(target):
    print 'Registring', target
    return target


class ApplyDecorator(type):
    def __new__(mcs, name, bases, attrs):
        decorator = attrs.pop('_decorator')
        cls = type(name, bases, attrs)
        return decorator(cls)

    def __init__(cls, name, bases, attrs, decorator=None):
        super().__init__(name, bases, attrs)


class Foo:
    __metaclass__ = ApplyDecorator
    _decorator = register

Python 3:

def register(target):
    print('Registring', target)
    return target


class ApplyDecorator(type):
    def __new__(mcs, name, bases, attrs, decorator):
        cls = type(name, bases, attrs)
        return decorator(cls)

    def __init__(cls, name, bases, attrs):
        super().__init__(name, bases, attrs)


class Foo(metaclass=ApplyDecorator, decorator=register):
    pass
Idan Arye
la source
1
Comme vous le dites, tout peut être fait avec un décorateur. La question est, quels sont les cas qui devraient être faits via des décorateurs de classe plutôt que des métaclasses.
Zaur Nasibov