281 votes

Décorateur de méthode de classe avec arguments personnels ?

Comment passer un champ de classe à un décorateur sur une méthode de classe en tant qu'argument ? Ce que je veux faire 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 soi n'existe pas pour le passage. self.url au décorateur. Existe-t-il un moyen de contourner ce problème ?

336voto

li.davidm Points 4375

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, il lit donc l'attribut à partir de celle-ci. Vous pouvez passer le nom de l'attribut sous forme de chaîne de caractères au décorateur et utiliser la fonction getattr si vous ne voulez 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

111voto

maxywb Points 583

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 s'imprime :

kitty!

49voto

Raphaël Points 479
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()

o my_rule2 : ok

16voto

Arwalk Points 188

Une autre option consisterait à abandonner le sucre syntaxique et à décorer dans les __init__ de la classe.

def countdown(number):
    def countdown_decorator(func):
        def func_wrapper():
            for index in reversed(range(1, number+1)):
                print(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!

10voto

dguerrero Points 179

Je sais que ce problème est assez ancien, mais la solution ci-dessous n'a jamais été proposée auparavant. Le problème ici est que vous ne pouvez pas accéder à self dans un bloc de classe, mais vous pouvez le faire dans une méthode de classe.

Créons un décorateur fictif pour répéter une fonction plusieurs fois.

import functools
def repeat(num_rep):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_rep):
                value = func(*args, **kwargs)
            return 
        return wrapper_repeat
    return decorator_repeat

class A:
    def __init__(self, times, name):
        self.times = times
        self.name = name

    def get_name(self):
        @repeat(num_rep=self.times)
        def _get_name():
            print(f'Hi {self.name}')
        _get_name()

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