Décorateurs de classe vs décorateurs de fonction [dupliquer]

Cette question a déjà une réponse ici:

En python, il y a deux façons de déclarer les décorateurs:

Basé sur la classe

class mydecorator(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *k, **kw):
        # before f actions
        self.f(*k, **kw)
        # after f actions

La Fonction de base

def mydecorator(f):
    def decorator(*k, **kw):
        # before f actions
        f(*k, **kw)
        # after f actions

    return decorator          

Y a-t-il une différence entre ces déclarations? Dans quels cas chacun d'entre eux doit être utilisé?

28
demandé sur martineau 2011-07-13 12:19:59

5 réponses

Si vous voulez garder l'état dans le décorateur, vous devez utiliser une classe.

Par exemple, cela ne fonctionne pas

def mydecorator(f):
    x = 0 
    def decorator():
        x += 1 # x is a nonlocal name and cant be modified
        return f(x)
    return decorator 

Il y a beaucoup de solutions de contournement pour cela, mais le plus simple est d'utiliser une classe

class mydecorator(object):
    def __init__(self, f):
        self.f = f
        self.x = 0

    def __call__(self, *k, **kw):
        self.x += 1
        return f(self.x)
18
répondu Jochen Ritzel 2011-07-13 08:57:13

Lorsque vous créez un appelable renvoyant un autre appelable, l'approche de fonction est plus facile et moins coûteuse. Il y a deux différences principales:

  1. l'approche de la fonction fonctionne automatiquement avec les méthodes, tandis que si vous utilisez votre approche de classe, vous devrez lire les descripteurs et définir une méthode __get__.
  2. l'approche de classe facilite le maintien de l'état. Vous pouvez utiliser une fermeture, en particulier dans Python 3, mais une approche de classe est généralement préférée pour garder état.

De plus, l'approche de fonction vous permet de retourner la fonction d'origine, après l'avoir modifiée ou stockée.

Cependant, un décorateur peut renvoyer autre chose qu'un "callable" ou quelque chose plus qu'un appelable. Avec une classe, vous pouvez:

  1. Ajoutez des méthodes et des propriétés à l'objet appelable décoré, ou implémentez des opérations sur eux (uh-oh).
  2. créer des descripteurs qui agissent d'une manière spéciale lorsqu'ils sont placés dans des classes (par exemple classmethod, property)
  3. Utilisez l'héritage pour implémenter des décorateurs similaires mais différents.

Si vous avez un doute, demandez-vous: voulez-vous que votre décorateur retourne une fonction qui agit exactement comme une fonction devrait? Utilisez une fonction renvoyant une fonction. Voulez-vous que votre décorateur retourne un objet personnalisé qui fait quelque chose de plus ou de différent de ce qu'une fonction fait? Créez une classe et utilisez-la comme décorateur.

17
répondu Rosh Oxymoron 2011-07-13 22:49:24

En fait, il n'y a pas de "deux façons". Il n'y a qu'une seule façon (définir un objet appelable) ou autant de façons qu'il y en a en python pour faire un objet appelable (ce pourrait être une méthode d'un autre objet, un résultat de l'expression lambda, un objet 'partiel', tout ce qui est appelable).

La définition de fonction est le moyen le plus simple de créer un objet appelable et, comme le plus simple, est probablement le meilleur dans la plupart des cas. L'utilisation d'une classe vous donne plus de possibilités de coder proprement des cas plus compliqués (même dans les cas les plus simples, il semble assez élégant), mais ce n'est pas évident ce qu'il fait.

6
répondu Jacek Konieczny 2011-07-13 08:46:01

Non, il y a (plus de) deux façons de créer des objets appelables. On est à def une fonction, qui est évidemment appelable. Une autre consiste à définir une méthode __call__ dans une classe, ce qui rendra les instances appelables. Et les classes elles-mêmes sont des objets appelables.

Un décorateur n'est rien de plus qu'un objet appelable, qui est destiné à accepter une fonction comme seul argument et renvoyer quelque chose d'appelable. La syntaxe suivante:

@decorate
def some_function(...):
    ...

Est juste une façon un peu plus agréable de écriture:

def some_function(...):
    ...

some_function = decorate(some_function)

L'exemple basé sur la classe que vous donnez n'est pas une fonction qui prend une fonction et renvoie une fonction, qui est le décorateur vanilla standard bog, c'est une classe initialisée avec une fonction dont les instances sont appelables. Pour moi, c'est un peu bizarre si vous ne l'utilisez pas réellement comme une classe (A-t-il d'autres méthodes? Fait de son changement d'état? En faites-vous plusieurs instances qui ont un comportement commun encapsulé par la classe?). Mais l'utilisation normale de votre décoré la fonction ne fera pas la différence (sauf si c'est un décorateur particulièrement invasif), alors faites tout ce qui vous semble plus naturel.

2
répondu Ben 2011-07-13 08:47:32

Testons-le!

test_class = """
class mydecorator_class(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *k, **kw):
        # before f actions
        print 'hi class'
        self.f(*k, **kw)
        print 'goodbye class'
        # after f actions

@mydecorator_class
def cls():
    print 'class'

cls()
"""

test_deco = """
def mydecorator_func(f):
    def decorator(*k, **kw):
        # before f actions
        print 'hi function'
        f(*k, **kw)
        print 'goodbye function'
        # after f actions
    return decorator

@mydecorator_func
def fun():
    print 'func'

fun()
"""

if __name__ == "__main__":
    import timeit
    r = timeit.Timer(test_class).timeit(1000)
    r2 = timeit.Timer(test_deco).timeit(1000)
    print r, r2

J'ai des résultats comme ceci: 0.0499339103699 0.0824959278107

Cela signifie que la classe déco 2 fois plus rapide?

1
répondu Nikolay Fominyh 2011-07-13 08:56:38