722 Stimmen

Dekorateure mit Parametern?

Ich habe ein Problem mit der Übertragung der Variablen insurance_mode durch den Decorator. Ich würde es mit der folgenden Decorator-Anweisung tun:

@execute_complete_reservation(True)
def test_booking_gta_object(self):
    self.test_select_gta_object()

aber leider funktioniert diese Anweisung nicht. Vielleicht gibt es eine bessere Möglichkeit, dieses Problem zu lösen.

def execute_complete_reservation(test_case,insurance_mode):
    def inner_function(self,*args,**kwargs):
        self.test_create_qsf_query()
        test_case(self,*args,**kwargs)
        self.test_select_room_option()
        if insurance_mode:
            self.test_accept_insurance_crosseling()
        else:
            self.test_decline_insurance_crosseling()
        self.test_configure_pax_details()
        self.test_configure_payer_details

    return inner_function

5 Stimmen

Dein Beispiel ist nicht syntaktisch korrekt. execute_complete_reservation erwartet zwei Parameter, aber du übergibst ihm nur einen. Dekorateure sind nur syntaktischer Zucker, um Funktionen in andere Funktionen einzuschließen. Siehe docs.python.org/reference/compound_stmts.html#function für die vollständige Dokumentation.

1195voto

t.dubrownik Punkte 11352

Die Syntax für Dekoratoren mit Argumenten ist etwas anders - der Dekorator mit Argumenten sollte eine Funktion zurückgeben, die eine Funktion annehmen wird und eine andere Funktion zurückgibt. Also sollte er wirklich einen normalen Dekorator zurückgeben. Ein bisschen verwirrend, oder? Was ich meine ist:

def decorator_factory(argument):
    def decorator(function):
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            result = function(*args, **kwargs)
            more_funny_stuff()
            return result
        return wrapper
    return decorator

Hier können Sie mehr zum Thema lesen - es ist auch möglich, dies mit aufrufbaren Objekten zu implementieren, und das wird dort ebenfalls erklärt.

0 Stimmen

Vielen Dank, Ihre Lösung passt besser zu meinem Problem - erklärt leicht, wie man Dekorateure mit Parametern erstellen kann

2 Stimmen

Ich habe das gerade mit Lambdas überall gemacht. (Lesen: Python ist genial!) :)

138 Stimmen

Ich frage mich, warum GVR es nicht umgesetzt hat, indem er die Parameter als nachfolgende Dekoriererargumente nach 'function' übergibt. 'Yo dawg ich habe gehört, dass du Closures magst...' usw.

531voto

srj Punkte 8823

Bearbeiten : Für ein tiefgreifendes Verständnis des geistigen Modells von Dekoratoren, schau dir dieses fantastische Pycon-Talk hier an. Die 30 Minuten sind es wert.

Eine Möglichkeit, über Dekoratoren mit Argumenten nachzudenken, ist

@decorator
def foo(*args, **kwargs):
    pass

übersetzt zu

foo = decorator(foo)

Wenn der Dekorator Argumente hatte,

@decorator_with_args(arg)
def foo(*args, **kwargs):
    pass

übersetzt zu

foo = decorator_with_args(arg)(foo)

decorator_with_args ist eine Funktion, die ein benutzerdefiniertes Argument akzeptiert und den tatsächlichen Dekorator zurückgibt (der auf die dekorierte Funktion angewendet wird).

Ich benutze einen einfachen Trick mit Partials, um meine Dekoratoren einfach zu machen

from functools import partial

def _pseudo_decor(fun, argument):
    def ret_fun(*args, **kwargs):
        # hier behandeln, z.B.
        print ("Dekoratorargument ist %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def foo(*args, **kwargs):
    pass

Update:

Oben wird foo zu real_decorator(foo)

Eine Auswirkung des Dekorators einer Funktion ist, dass der Name foo bei der Deklaration des Dekorators überschrieben wird. foo wird "überschrieben" durch das, was von real_decorator zurückgegeben wird. In diesem Fall ein neues Funktionsobjekt.

Alle Metadaten von foo werden überschrieben, insbesondere die Docstring und der Funktionsname.

>>> print(foo)
.ret_fun at 0x10666a2f0>

functools.wraps gibt uns eine bequeme Methode, um die Docstring und den Namen zur zurückgegebenen Funktion zu "heben".

from functools import partial, wraps

def _pseudo_decor(fun, argument):
    # Magische Soße, um den Namen und die Docstring der Funktion hervorzuheben
    @wraps(fun)
    def ret_fun(*args, **kwargs):
        # vor der Funktionsausführung hier, z.B.
        print("Dekoratorargument ist %s" % str(argument))
        zurückgegebener_wert =  fun(*args, **kwargs)
        # nach der Ausführung hier, z.B.
        print("Zurückgegebener Wert ist %s" % zurückgegebener_wert)
        return zurückgegebener_wert

    return ret_fun

real_decorator1 = partial(_pseudo_decor, argument="some_arg")
real_decorator2 = partial(_pseudo_decor, argument="some_other_arg")

@real_decorator1
def bar(*args, **kwargs):
    pass

>>> print(bar)

>>> bar(1,2,3, k="v", x="z")
Dekoratorargument ist some_arg
Zurückgegebener Wert ist None

9 Stimmen

Ihre Antwort erklärte perfekt die inhärente Orthogonalität des Dekorateurs, vielen Dank

0 Stimmen

Könnten Sie @functools.wraps hinzufügen?

1 Stimmen

@Mr_and_Mrs_D, Ich habe den Beitrag mit einem Beispiel mit functool.wraps aktualisiert. Wenn ich es im Beispiel hinzufüge, könnte es die Leser weiter verwirren.

167voto

Ross R Punkte 8053

Hier ist eine leicht modifizierte Version von t.dubrownik's Antwort. Warum?

  1. Als allgemeines Template sollten Sie den Rückgabewert aus der Originalfunktion zurückgeben.
  2. Dies ändert den Namen der Funktion, was andere Dekorateure / Code beeinflussen könnte.

Also verwenden Sie @functools.wraps():

from functools import wraps

def create_decorator(argument):
    def decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            witzige_sachen()
            etwas_mit_argument(argument)
            retval = function(*args, **kwargs)
            mehr_witzige_sachen()
            return retval
        return wrapper
    return decorator

5 Stimmen

Ich habe genau das gemacht, aber auf AWS Lambdas mit Flask funktioniert es nicht: Python 3.8 gibt diesen Fehler zurück: AssertionError: Ansichtsfunktionszuordnung überschreibt eine vorhandene Endpunkt-Funktion: authorization_required_wrapper

4 Stimmen

Dies ist meine Lieblingsantwort hier, da Wraps wichtig sind.

1 Stimmen

Vielen Dank für diese Antwort, es hat mich eine Weile gedauert, um dieses Konzept zu begreifen... ba dum tsk... lol. Also das Schlüsselkonzept hier für mich war, dass dies 3 Ebenen tief ist. Ich habe einige weitere verwandte Informationen gefunden: realpython.com/primer-on-python-decorators/…

125voto

Dacav Punkte 12500

Ich möchte eine Idee zeigen, die meiner Meinung nach ziemlich elegant ist. Die von t.dubrownik vorgeschlagene Lösung zeigt ein Muster, das immer dasselbe ist: Sie benötigen den dreischichtigen Wrapper unabhängig davon, was der Dekorierer tut.

Also dachte ich, das ist eine Aufgabe für einen Meta-Dekorierer, das heißt, ein Dekorierer für Dekorierer. Da ein Dekorierer eine Funktion ist, funktioniert er tatsächlich wie ein regulärer Dekorierer mit Argumenten:

def parametrisiert(dec):
    def schicht(*args, **kwargs):
        def ersetzen(f):
            return dec(f, *args, **kwargs)
        return ersetzen
    return schicht

Dies kann auf einen regulären Dekorierer angewendet werden, um Parameter hinzuzufügen. Nehmen wir zum Beispiel an, wir haben den Dekorierer, der das Ergebnis einer Funktion verdoppelt:

def verdoppeln(f):
    def aux(*xs, **kws):
        return 2 * f(*xs, **kws)
    return aux

@verdoppeln
def funktion(a):
    return 10 + a

print funktion(3)    # Gibt 26 aus, nämlich 2 * (10 + 3)

Mit @parametrisiert können wir einen generischen @multiplizieren Dekorierer mit einem Parameter erstellen

@parametrisiert
def multiplizieren(f, n):
    def aux(*xs, **kws):
        return n * f(*xs, **kws)
    return aux

@multiplizieren(2)
def funktion(a):
    return 10 + a

print funktion(3)    # Gibt 26 aus

@multiplizieren(3)
def funktion_erneut(a):
    return 10 + a

print funktion(3)          # Gibt weiterhin 26 aus
print funktion_erneut(3)    # Gibt 39 aus, nämlich 3 * (10 + 3)

Üblicherweise ist das erste Argument eines parametrisierten Dekorierers die Funktion, während die restlichen Argumente dem Parameter des parametrisierten Dekorierers entsprechen.

Ein interessantes Anwendungsbeispiel könnte ein Typ-sicherer assertiver Dekorierer sein:

import itertools as it

@parametrisiert
def typen(f, *typen):
    def rep(*args):
        for a, t, n in zip(args, typen, it.count()):
            if type(a) is not t:
                raise TypeError('Wert %d hat nicht den Typ %s. Stattdessen %s' %
                    (n, t, type(a))
                )
        return f(*args)
    return rep

@typen(str, int)  # arg1 ist str, arg2 ist int
def string_multiplizieren(text, mal):
    return text * mal

print(string_multiplizieren('hallo', 3))    # Gibt hallohallohallo aus
print(string_multiplizieren(3, 3))          # Scheitert kläglich mit TypeError

Eine abschließende Anmerkung: Hier verwende ich nicht functools.wraps für die Wrapper-Funktionen, aber ich würde empfehlen, es immer zu verwenden.

5 Stimmen

Habe dies nicht genau verwendet, aber es hat mir geholfen, das Konzept zu verstehen :) Danke!

0 Stimmen

Ich habe das versucht und hatte einige Probleme.

0 Stimmen

@Jeff, könntest du uns die Art der Probleme teilen, die du hattest?

49voto

Shital Shah Punkte 54846

Das Schreiben eines Dekorateurs, der mit und ohne Parameter funktioniert, ist eine Herausforderung, da Python in diesen beiden Fällen vollständig unterschiedliches Verhalten erwartet! Viele Antworten haben versucht, dies zu umgehen, und unten ist eine Verbesserung der Antwort von @norok2. Speziell eliminiert diese Variation die Verwendung von locals().

Dem gleichen Beispiel folgend wie bei @norok2:

import functools

def multiplying(f_py=None, factor=1):
    assert callable(f_py) or f_py is None
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(f_py) if callable(f_py) else _decorator

@multiplying
def summing(x): return sum(x)

print(summing(range(10)))
# 45

@multiplying()
def summing(x): return sum(x)

print(summing(range(10)))
# 45

@multiplying(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

Mit diesem Code spielen.

Der Haken ist, dass der Benutzer Schlüssel-Wert-Paare von Parametern anstelle von Positionsparametern bereitstellen muss und der erste Parameter reserviert ist.

3 Stimmen

Das ist verdammt genial.

0 Stimmen

Bitte erläutern Sie "Python erwartet in diesen beiden Fällen völlig unterschiedliches Verhalten"

0 Stimmen

Wie wäre es, wenn Sie das Rückgabeverhalten der Multiplikationsfunktion von return _decorator(f_py) if callable(f_py) else _decorator in return _decorator(f_py) if f_py else _decorator oder return _decorator if f_py is None else _decorator(f_py) ändern, da bereits festgestellt wurde, dass es entweder ein Aufruf oder None ist. Dies sollte "effizienter" sein als ein zweites Aufrufen von callable.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X