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é?
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)
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:
- 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__
. - 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:
- Ajoutez des méthodes et des propriétés à l'objet appelable décoré, ou implémentez des opérations sur eux (uh-oh).
- créer des descripteurs qui agissent d'une manière spéciale lorsqu'ils sont placés dans des classes (par exemple
classmethod
,property
) - 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.
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.
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.
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?