38 votes

Classes de décorateurs en Python

Je souhaite construire des classes pour les utiliser comme décorateurs en respectant les principes suivants :

  1. Il devrait être possible d'empiler plusieurs décorateurs de classe de ce type sur une fonction.
  2. Le pointeur de nom de fonction qui en résulte ne devrait pas pouvoir être distingué de la même fonction sans décorateur, à l'exception peut-être du type ou de la classe dont il s'agit.
  3. Le fait de commander aux décorateurs ne devrait pas être pertinent, à moins que les décorateurs ne l'exigent. Par exemple, des décorateurs indépendants peuvent être appliqués dans n'importe quel ordre.

C'est pour un projet Django, et le cas spécifique sur lequel je travaille actuellement, la méthode a besoin de 2 décorateurs, et d'apparaître comme une fonction python normale :

@AccessCheck
@AutoTemplate
def view(request, item_id) {}

@AutoTemplate modifie la fonction de sorte qu'au lieu de renvoyer une réponse HttpResponse, elle renvoie simplement un dictionnaire à utiliser dans le contexte. Un RequestContext est utilisé et le nom du modèle est déduit du nom de la méthode et du module.

@AccessCheck ajoute des contrôles supplémentaires sur l'utilisateur en fonction de l'identifiant de l'article.

Je suppose qu'il s'agit simplement de faire en sorte que le constructeur soit correct et de copier les attributs appropriés, mais de quels attributs s'agit-il ?

Le décorateur suivant ne fonctionnera pas comme je l'ai décrit :

class NullDecl (object):
    def __init__ (self, func):
        self.func = func
    def __call__ (self, * args):
        return self.func (*args)

Comme le montre le code suivant :

@NullDecl
@NullDecl
def decorated():
    pass

def pure():
    pass

# results in set(['func_closure', 'func_dict', '__get__', 'func_name',
# 'func_defaults', '__name__', 'func_code', 'func_doc', 'func_globals'])
print set(dir(pure)) - set(dir(decorated));

En outre, essayez d'ajouter "print func. nom "dans le constructeur NullDecl, et cela fonctionnera pour le premier décorateur, mais pas pour le second - car le nom sera manquant.

Raffiné eduffy La réponse de l'auteur est un peu plus détaillée, et elle semble fonctionner assez bien :

class NullDecl (object):
    def __init__ (self, func):
        self.func = func
        for n in set(dir(func)) - set(dir(self)):
            setattr(self, n, getattr(func, n))

    def __call__ (self, * args):
        return self.func (*args)
    def __repr__(self):
        return self.func

23voto

eduffy Points 17061

Une classe de décorateur qui ne fait rien ressemblerait à ceci :

class NullDecl (object):
   def __init__ (self, func):
      self.func = func
      for name in set(dir(func)) - set(dir(self)):
        setattr(self, name, getattr(func, name))

   def __call__ (self, *args):
      return self.func (*args)

Vous pouvez ensuite l'appliquer normalement :

@NullDecl
def myFunc (x,y,z):
   return (x+y)/z

10voto

f3lix Points 13634

En module décorateur vous aide à écrire des décorateurs préservant la signature.

Et le Bibliothèque PythonDecorator pourrait fournir des exemples utiles pour les décorateurs.

7voto

codeape Points 38576

Pour créer un décorateur qui enveloppe les fonctions de manière à les rendre indiscernables de la fonction d'origine, utilisez la fonction functools.wraps .

Exemple :

def mydecorator(func):
    @functools.wraps(func):
    def _mydecorator(*args, **kwargs):
        do_something()
        try:
            return func(*args, **kwargs)
        finally:
            clean_up()
    return _mydecorator

# ... and with parameters
def mydecorator(param1, param2):
    def _mydecorator(func):
        @functools.wraps(func)
        def __mydecorator(*args, **kwargs):
            do_something(param1, param2)
            try:
                return func(*args, **kwargs)
            finally:
                clean_up()
        return __mydecorator
    return _mydecorator

(ma préférence personnelle est de créer des décorateurs en utilisant des fonctions, et non des classes)

L'ordre des décorateurs est le suivant :

@d1
@d2
def func():
    pass

# is equivalent to
def func():
    pass

func = d1(d2(func))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X