Классный метод декоратор с собственными аргументами?


154

Как передать поле класса декоратору метода класса в качестве аргумента? Что я хочу сделать, это что-то вроде:

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

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

Он жалуется, что self не существует для передачи self.urlдекоратору. Есть ли способ обойти это?


Это пользовательский декоратор, которым вы управляете, или тот, который вы не можете изменить?
Джоэл Корнетт

Это мой декоратор, поэтому у меня есть полный контроль над ним
Марк

Он вызывается до init, я думаю, что это проблема ...
Джоран Бизли

7
Проблема в том, что self не существует во время определения функции. Вы должны превратить это в частичную функцию.
Сурьма

Ответы:


209

Да. Вместо передачи атрибута экземпляра во время определения класса, проверьте его во время выполнения:

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

Декоратор перехватывает аргументы метода; первый аргумент - это экземпляр, поэтому он считывает атрибут этого. Вы можете передать имя атрибута в виде строки декоратору и использовать его, getattrесли не хотите жестко задавать имя атрибута:

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

выход: my_rule2: хорошо


@raphael В этой настройке я не могу получить доступ к _lambda или шаблону. Как я могу исправить это.
Джонатан

1
@ Рафаэль: Как я могу сделать то же самое для метода класса, так как здесь все методы являются методами экземпляра.
Апурва Кункулол

38

Более краткий пример может быть следующим:

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

Который напечатает:

kitty!

6

Еще один вариант - отказаться от синтаксического сахара и украсить его в __init__классе.

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

который напечатал бы

3
2
1
im doing stuff!

4

Ты не можешь Там нет selfв теле класса, потому что нет экземпляра существует. Вам нужно было бы передать его, скажем, strсодержащее имя атрибута для поиска в экземпляре, который затем может сделать возвращаемая функция, или полностью использовать другой метод.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.