Dekorator metody klasowej z argumentami własnymi?

154

Jak przekazać pole klasy do dekoratora w metodzie klasy jako argument? To, co chcę zrobić, to coś takiego:

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

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

Narzeka, że ​​jaźń nie istnieje do przekazania self.urldekoratorowi. Czy jest na to sposób?

znak
źródło
Czy to niestandardowy dekorator, nad którym masz kontrolę, czy taki, którego nie możesz zmienić?
Joel Cornett
To mój dekorator, więc mam nad nim pełną kontrolę
Mark
Zostaje wywołany przed init. Myślę, że jest problem ...
Joran Beasley
7
Problem w tym, że jaźń nie istnieje w czasie definiowania funkcji. Musisz uczynić z tego funkcję częściową.
Antymon

Odpowiedzi:

208

Tak. Zamiast przekazywać atrybut instancji w czasie definiowania klasy, sprawdź go w czasie wykonywania:

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

Dekorator przechwytuje argumenty metody; pierwszym argumentem jest instancja, więc odczytuje z niej atrybut. Możesz przekazać nazwę atrybutu jako ciąg do dekoratora i użyć, getattrjeśli nie chcesz zakodować na stałe nazwy atrybutu:

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
źródło
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()

ouput: my_rule2: ok

Raphaël
źródło
@raphael W tej konfiguracji nie mam dostępu do _lambda ani wzorca. Jak mogę temu zaradzić.
Jonathan
1
@Raphael: Jak mogę zrobić to samo dla metody klasowej, skoro tutaj wszystkie metody są metodami instancji.
Apurva Kunkulol
38

Bardziej zwięzły przykład może wyglądać następująco:

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

Który wydrukuje:

kitty!
maxywb
źródło
6

Inną opcją byłoby porzucenie cukru syntaktycznego i udekorowanie w __init__klasie.

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

który by się wydrukował

3
2
1
im doing stuff!
Arwalk
źródło
4

Nie możesz. W selftreści klasy nie ma, ponieważ nie istnieje instancja. Musiałbyś przekazać go, powiedzmy, strzawierający nazwę atrybutu, aby wyszukać w instancji, co może następnie wykonać zwrócona funkcja, lub użyć całkowicie innej metody.

juliański
źródło