Décorateur de méthode de classe avec auto-arguments?

154

Comment passer un champ de classe à un décorateur sur une méthode de classe en tant qu'argument? Ce que je veux faire, c'est quelque chose comme:

class Client(object):
    def __init__(self, url):
        self.url = url

    @check_authorization("some_attr", self.url)
    def get(self):
        do_work()

Il se plaint que le moi n'existe pas pour passer self.urlau décorateur. Y a-t-il un moyen de contourner ceci?

marque
la source
Est-ce un décorateur personnalisé sur lequel vous contrôlez ou que vous ne pouvez pas changer?
Joel Cornett
C'est mon décorateur, donc j'ai un contrôle total dessus
Mark
Il est appelé avant init, je pense que c'est le problème ...
Joran Beasley
7
Le problème est que le soi n'existe pas au moment de la définition de la fonction. Vous devez en faire une fonction partielle.
Antimoine

Réponses:

209

Oui. Au lieu de transmettre l'attribut d'instance au moment de la définition de la classe, vérifiez-le au moment de l'exécution:

def check_authorization(f):
    def wrapper(*args):
        print args[0].url
        return f(*args)
    return wrapper

class Client(object):
    def __init__(self, url):
        self.url = url

    @check_authorization
    def get(self):
        print 'get'

>>> Client('http://www.google.com').get()
http://www.google.com
get

Le décorateur intercepte les arguments de la méthode; le premier argument est l'instance, donc il lit l'attribut de cela. Vous pouvez transmettre le nom de l'attribut sous forme de chaîne au décorateur et l'utiliser getattrsi vous ne souhaitez pas coder en dur le nom de l'attribut:

def check_authorization(attribute):
    def _check_authorization(f):
        def wrapper(self, *args):
            print getattr(self, attribute)
            return f(self, *args)
        return wrapper
    return _check_authorization
li.davidm
la source
38
from re import search
from functools import wraps

def is_match(_lambda, pattern):
    def wrapper(f):
        @wraps(f)
        def wrapped(self, *f_args, **f_kwargs):
            if callable(_lambda) and search(pattern, (_lambda(self) or '')): 
                f(self, *f_args, **f_kwargs)
        return wrapped
    return wrapper

class MyTest(object):

    def __init__(self):
        self.name = 'foo'
        self.surname = 'bar'

    @is_match(lambda x: x.name, 'foo')
    @is_match(lambda x: x.surname, 'foo')
    def my_rule(self):
        print 'my_rule : ok'

    @is_match(lambda x: x.name, 'foo')
    @is_match(lambda x: x.surname, 'bar')
    def my_rule2(self):
        print 'my_rule2 : ok'



test = MyTest()
test.my_rule()
test.my_rule2()

sortie: my_rule2: ok

Raphaël
la source
@raphael Dans cette configuration, je n'arrive pas à accéder à _lambda ou pattern. Comment puis-je y remédier.
Jonathan
1
@Raphael: Comment puis-je faire la même chose pour une méthode de classe, puisque ici toutes les méthodes sont des méthodes d'instance.
Apurva Kunkulol
38

Un exemple plus concis pourrait être le suivant:

#/usr/bin/env python3
from functools import wraps

def wrapper(method):
    @wraps(method)
    def _impl(self, *method_args, **method_kwargs):
        method_output = method(self, *method_args, **method_kwargs)
        return method_output + "!"
    return _impl

class Foo:
    @wrapper
    def bar(self, word):
        return word

f = Foo()
result = f.bar("kitty")
print(result)

Qui imprimera:

kitty!
maxywb
la source
6

Une autre option serait d'abandonner le sucre syntaxique et de décorer dans __init__la classe.

def countdown(number):
    def countdown_decorator(func):
        def func_wrapper():
            for index in reversed(range(1, number+1)):
                print("{}".format(index))
            func()
        return func_wrapper
    return countdown_decorator

class MySuperClass():
    def __init__(self, number):
        self.number = number
        self.do_thing = countdown(number)(self.do_thing)

    def do_thing(self):
        print('im doing stuff!')


myclass = MySuperClass(3)

myclass.do_thing()

qui imprimerait

3
2
1
im doing stuff!
Arwalk
la source
4

Vous ne pouvez pas. Il n'y a pas selfdans le corps de la classe, car aucune instance n'existe. Vous devez le transmettre, par exemple, un strcontenant le nom de l'attribut à rechercher sur l'instance, ce que la fonction retournée peut alors faire, ou utiliser une méthode entièrement différente.

julien
la source