132 votes

Un décorateur Python d'une méthode d'instance peut-il accéder à la classe ?

Bonjour, j'ai quelque chose qui ressemble à peu près à ce qui suit. En gros, j'ai besoin d'accéder à la classe d'une méthode d'instance à partir d'un décorateur utilisé sur la méthode d'instance dans sa définition.

def decorator(view):
    # do something that requires view's class
    print view.im_class
    return view

class ModelA(object):
    @decorator
    def a_method(self):
        # do some stuff
        pass

Le code tel quel donne

AttributeError: 'function' object has no attribute 'im_class'

J'ai trouvé des questions/réponses similaires - Le décorateur Python fait oublier à une fonction qu'elle appartient à une classe et Obtenir une classe dans le décorateur Python - mais elles reposent sur une solution de contournement qui saisit l'instance au moment de l'exécution en récupérant le premier paramètre. Dans mon cas, je vais appeler la méthode en me basant sur les informations glanées dans sa classe, je ne peux donc pas attendre qu'un appel arrive.

Merci.

78voto

Dave Kirby Points 12310

Si vous utilisez Python 2.6 ou une version ultérieure, vous pouvez utiliser un décorateur de classe, peut-être quelque chose comme ceci (avertissement : code non testé).

def class_decorator(cls):
   for name, method in cls.__dict__.iteritems():
        if hasattr(method, "use_class"):
            # do something with the method and class
            print name, cls
   return cls

def method_decorator(view):
    # mark the method as something that requires view's class
    view.use_class = True
    return view

@class_decorator
class ModelA(object):
    @method_decorator
    def a_method(self):
        # do some stuff
        pass

Le décorateur de méthode marque la méthode comme étant intéressante en ajoutant un attribut "use_class". Les fonctions et les méthodes sont également des objets, vous pouvez donc leur attacher des métadonnées supplémentaires.

Après la création de la classe, le décorateur de classe passe en revue toutes les méthodes et fait ce qui est nécessaire sur les méthodes qui ont été marquées.

Si vous voulez que toutes les méthodes soient affectées, vous pouvez laisser de côté le décorateur de méthode et utiliser uniquement le décorateur de classe.

16voto

Mark Visser Points 615

Comme d'autres l'ont souligné, la classe n'a pas été créée au moment où le décorateur est appelé. Cependant il est possible d'annoter l'objet de la fonction avec les paramètres du décorateur, puis de redécorer la fonction dans la métaclasse __new__ méthode. Vous devrez accéder à la fonction __dict__ attribut directement, comme au moins pour moi, func.foo = 1 a donné lieu à une AttributeError.

5voto

Ants Aasma Points 22921

Le problème est que lorsque le décorateur est appelé, la classe n'existe pas encore. Essayez ceci :

def loud_decorator(func):
    print("Now decorating %s" % func)
    def decorated(*args, **kwargs):
        print("Now calling %s with %s,%s" % (func, args, kwargs))
        return func(*args, **kwargs)
    return decorated

class Foo(object):
    class __metaclass__(type):
        def __new__(cls, name, bases, dict_):
            print("Creating class %s%s with attributes %s" % (name, bases, dict_))
            return type.__new__(cls, name, bases, dict_)

    @loud_decorator
    def hello(self, msg):
        print("Hello %s" % msg)

Foo().hello()

Ce programme produira un résultat :

Now decorating <function hello at 0xb74d35dc>
Creating class Foo(<type 'object'>,) with attributes {'__module__': '__main__', '__metaclass__': <class '__main__.__metaclass__'>, 'hello': <function decorated at 0xb74d356c>}
Now calling <function hello at 0xb74d35dc> with (<__main__.Foo object at 0xb74ea1ac>, 'World'),{}
Hello World

Comme vous le voyez, vous allez devoir trouver une autre façon de faire ce que vous voulez.

5voto

Ross Rogers Points 5619

Comme Ants l'a indiqué, vous ne pouvez pas obtenir une référence à la classe depuis l'intérieur de la classe. Cependant, si vous êtes intéressé par la distinction entre les différentes classes (et non par la manipulation de l'objet de type classe), vous pouvez passer une chaîne pour chaque classe. Vous pouvez également passer tous les autres paramètres que vous souhaitez au décorateur en utilisant des décorateurs de style classe.

class Decorator(object):
    def __init__(self,decoratee_enclosing_class):
        self.decoratee_enclosing_class = decoratee_enclosing_class
    def __call__(self,original_func):
        def new_function(*args,**kwargs):
            print 'decorating function in ',self.decoratee_enclosing_class
            original_func(*args,**kwargs)
        return new_function

class Bar(object):
    @Decorator('Bar')
    def foo(self):
        print 'in foo'

class Baz(object):
    @Decorator('Baz')
    def foo(self):
        print 'in foo'

print 'before instantiating Bar()'
b = Bar()
print 'calling b.foo()'
b.foo()

Imprimés :

before instantiating Bar()
calling b.foo()
decorating function in  Bar
in foo

Aussi, voir la page de Bruce Eckel sur les décorateurs.

4voto

charlax Points 3653

Quoi flasque-classe est de créer un cache temporaire qu'il stocke sur la méthode, puis il utilise autre chose (le fait que Flask enregistre les classes en utilisant une méthode register ) pour envelopper réellement la méthode.

Vous pouvez réutiliser ce modèle, en utilisant cette fois une métaclasse afin de pouvoir envelopper la méthode au moment de l'importation.

def route(rule, **options):
    """A decorator that is used to define custom routes for methods in
    FlaskView subclasses. The format is exactly the same as Flask's
    `@app.route` decorator.
    """

    def decorator(f):
        # Put the rule cache on the method itself instead of globally
        if not hasattr(f, '_rule_cache') or f._rule_cache is None:
            f._rule_cache = {f.__name__: [(rule, options)]}
        elif not f.__name__ in f._rule_cache:
            f._rule_cache[f.__name__] = [(rule, options)]
        else:
            f._rule_cache[f.__name__].append((rule, options))

        return f

    return decorator

sur la classe réelle (vous pourriez faire de même en utilisant une métaclasse) :

@classmethod
def register(cls, app, route_base=None, subdomain=None, route_prefix=None,
             trailing_slash=None):

    for name, value in members:
        proxy = cls.make_proxy_method(name)
        route_name = cls.build_route_name(name)
        try:
            if hasattr(value, "_rule_cache") and name in value._rule_cache:
                for idx, cached_rule in enumerate(value._rule_cache[name]):
                    # wrap the method here

Source : https://github.com/apiguy/flask-classy/blob/master/flask_classy.py

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