Klassenmethode Dekorateur mit Selbstargumenten?

154

Wie übergebe ich ein Klassenfeld als Argument an einen Dekorateur für eine Klassenmethode? Was ich tun möchte, ist so etwas wie:

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

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

Es beschwert sich, dass es kein Selbst gibt, um es self.urlan den Dekorateur weiterzugeben. Gibt es einen Weg, dies zu umgehen?

Kennzeichen
quelle
Ist das ein benutzerdefinierter Dekorateur, über den Sie die Kontrolle haben, oder einer, den Sie nicht ändern können?
Joel Cornett
Es ist mein Dekorateur, also habe ich die vollständige Kontrolle darüber
Mark
Es wird vor init aufgerufen Ich denke, das ist das Problem ...
Joran Beasley
7
Das Problem ist, dass self zum Zeitpunkt der Funktionsdefinition nicht existiert. Sie müssen es zu einer Teilfunktion machen.
Antimon

Antworten:

208

Ja. Anstatt das Instanzattribut zur Zeit der Klassendefinition zu übergeben, überprüfen Sie es zur Laufzeit:

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

Der Dekorateur fängt die Methodenargumente ab. Das erste Argument ist die Instanz, also liest es das Attribut davon ab. Sie können den Attributnamen als Zeichenfolge an den Dekorateur übergeben und verwenden, getattrwenn Sie den Attributnamen nicht fest codieren möchten:

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

Ausgabe: my_rule2: ok

Raphaël
quelle
@raphael In diesem Setup kann ich anscheinend nicht auf _lambda oder pattern zugreifen. Wie kann ich das beheben?
Jonathan
1
@ Raphael: Wie kann ich dasselbe für eine Klassenmethode tun, da hier alle Methoden Instanzmethoden sind.
Apurva Kunkulol
38

Ein prägnanteres Beispiel könnte sein:

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

Welches wird gedruckt:

kitty!
maxywb
quelle
6

Eine andere Möglichkeit wäre, den syntaktischen Zucker aufzugeben und in __init__der Klasse zu dekorieren .

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

was würde drucken

3
2
1
im doing stuff!
Arwalk
quelle
4

Das kannst du nicht. Es gibt keine selfim Klassenkörper, da keine Instanz existiert. Sie müssten es beispielsweise übergeben, strdas den Attributnamen enthält, um nach der Instanz zu suchen, was die zurückgegebene Funktion dann tun kann, oder eine ganz andere Methode verwenden.

julianisch
quelle