Decorador de método de clase con argumentos propios?


154

¿Cómo paso un campo de clase a un decorador en un método de clase como argumento? Lo que quiero hacer es algo como:

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

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

Se queja de que el yo no existe para pasar self.urlal decorador. ¿Hay alguna forma de evitar esto?


¿Es un decorador personalizado sobre el que tienes control o uno que no puedes cambiar?
Joel Cornett

Es mi decorador, así que tengo control total sobre él
Mark

Se llama antes de init, creo que es el problema ...
Joran Beasley

77
El problema es que el self no existe en el momento de la definición de la función. Necesitas convertirlo en una función parcial.
Antimonio

Respuestas:


209

Si. En lugar de pasar el atributo de instancia en el momento de la definición de clase, verifíquelo en tiempo de ejecución:

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

El decorador intercepta los argumentos del método; El primer argumento es la instancia, por lo que lee el atributo de eso. Puede pasar el nombre del atributo como una cadena al decorador y usarlo getattrsi no desea codificar el nombre del atributo:

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

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()

salida: my_rule2: ok


@raphael En esta configuración, parece que no puedo acceder a _lambda o al patrón. ¿Cómo puedo remediar eso?
Jonathan

1
@Raphael: ¿Cómo puedo hacer lo mismo para un método de clase, ya que aquí todos los métodos son métodos de instancia?
Apurva Kunkulol

38

Un ejemplo más conciso podría ser el siguiente:

#/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)

Que imprimirá:

kitty!

6

Otra opción sería abandonar el azúcar sintáctico y decorar en __init__la clase.

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()

que imprimiría

3
2
1
im doing stuff!

4

No puedes No hay selfen el cuerpo de la clase, porque no existe una instancia. Tendría que pasarlo, por ejemplo, que strcontenga el nombre del atributo para buscar en la instancia, que la función devuelta puede hacer, o usar un método completamente diferente.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.