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.

7voto

run_the_race Punkte 2073

Tolle Antworten oben. Dieses illustriert auch @wraps, das die Doc-String und den Funktionsnamen der Originalfunktion nimmt und sie auf die neue umhüllte Version anwendet:

from functools import wraps

def decorator_func_with_args(arg1, arg2):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            print("Vor der Originalfunktion mit Dekorator-Argumenten:", arg1, arg2)
            result = f(*args, **kwargs)
            print("Nach der Originalfunktion ausgeführt")
            return result
        return wrapper
    return decorator

@decorator_func_with_args("foo", "bar")
def hello(name):
    """Eine Funktion, die eine Begrüßung an den bereitgestellten Namen druckt.
    """
    print('hello ', name)
    return 42

print("Skript startet..")
x = hello('Bob')
print("Der Wert von x ist:", x)
print("Die Docstring der umhüllten Funktion ist:", hello.__doc__)
print("Der Name der umhüllten Funktion ist:", hello.__name__)

Druckt:

Skript startet..
Vor der Originalfunktion mit Dekorator-Argumenten: foo bar
hello  Bob
Nach der Originalfunktion ausgeführt
Der Wert von x ist: 42
Die Docstring der umhüllten Funktion ist: Eine Funktion, die eine Begrüßung an den bereitgestellten Namen druckt.
Der Name der umhüllten Funktion ist: hello

4voto

Samuel Muldoon Punkte 1262

Es ist allgemein bekannt, dass die folgenden beiden Code-Stücke nahezu äquivalent sind:

@dec
def foo():
    pass    
foo = dec(foo)

############################################
foo = dec(foo)

Ein häufiger Fehler ist zu glauben, dass @ einfach das linke Argument verbirgt.

@dec(1, 2, 3)
def foo():
    pass    
###########################################
foo = dec(foo, 1, 2, 3)

Es wäre viel einfacher, Dekoratoren zu schreiben, wenn das obige Beispiel zeigt, wie @ funktioniert. Leider ist das nicht so gemacht.


Betrachten Sie einen Dekorator Warten, der die Ausführung des Programms um einige Sekunden verzögert. Wenn Sie keine Wartezeit übergeben, beträgt der Standardwert 1 Sekunde. Verwendungsfälle werden unten gezeigt.

##################################################
@Warten
def print_something(something):
    print(something)

##################################################
@Warten(3)
def print_something_else(something_else):
    print(something_else)

##################################################
@Warten(delay=3)
def print_something_else(something_else):
    print(something_else)

Wenn Wait ein Argument hat, wie z. B. @Warten(3), wird der Aufruf von Warten(3) vor allem anderen ausgeführt.

Das heißt, die folgenden beiden Code-Stücke sind äquivalent

@Warten(3)
def print_something_else(something_else):
    print(something_else)

###############################################
return_value = Warten(3)
@return_value
def print_something_else(something_else):
    print(something_else)

Das ist ein Problem.

if 'Warten' keine Argumente hat:
    'Warten' ist der Dekorator.
sonst: # 'Warten' empfängt Argumente
    'Warten' ist nicht der eigentliche Dekorator.
    Stattdessen **gibt** 'Warten' den Dekorator zurück

Eine Lösung wird unten gezeigt:

Beginnen wir damit, die folgende Klasse zu erstellen: DelayedDecorator:

class DelayedDecorator:
    def __init__(i, cls, *args, **kwargs):
        print("Verzögerter Dekorator __init__", cls, args, kwargs)
        i._cls = cls
        i._args = args
        i._kwargs = kwargs
    def __call__(i, func):
        print("Verzögerter Dekorator __call__", func)
        if not (callable(func)):
            import io
            with io.StringIO() as ss:
                print(
                    "Wenn nur ein Eingang, muss der Eingang aufrufbar sein",
                    "Stattdessen erhalten:",
                    repr(func),
                    sep="\n",
                    file=ss
                )
                msg = ss.getvalue()
            raise TypeError(msg)
        return i._cls(func, *i._args, **i._kwargs)

Jetzt können wir Dinge wie folgt schreiben:

 dec = DelayedDecorator(Warten, delay=4)
 @dec
 def delayed_print(something):
    print(something)

Beachten Sie, dass:

  • dec akzeptiert keine mehreren Argumente.
  • dec akzeptiert nur die zu umwickelnde Funktion.

    import inspect class PolyArgDecoratorMeta(type): def call(Warten, *args, **kwargs): try: arg_count = len(args) if (arg_count == 1): if callable(args[0]): Superklasse = inspect.getmro(PolyArgDecoratorMeta)[1] r = Superklasse.call(Warten, args[0]) else: r = DelayedDecorator(Warten, *args, **kwargs) else: r = DelayedDecorator(Warten, *args, **kwargs) finally: pass return r

    import time class Warten(metaclass=PolyArgDecoratorMeta): def init(i, func, delay = 2): i._func = func i._delay = delay

    def __call__(i, *args, **kwargs):
        time.sleep(i._delay)
        r = i._func(*args, **kwargs)
        return r 

Die folgenden beiden Code-Beispiele sind äquivalent:

@Warten
def print_something(something):
     print(something)

##################################################

def print_something(something):
    print(something)
print_something = Warten(print_something)

Wir können "etwas" sehr langsam in die Konsole drucken, wie folgt:

print_something("etwas")

#################################################
@Warten(delay=1)
def print_something_else(something_else):
    print(something_else)

##################################################
def print_something_else(something_else):
    print(something_else)

dd = DelayedDecorator(Warten, delay=1)
print_something_else = dd(print_something_else)

##################################################

print_something_else("etwas")

Letzte Hinweise

Es mag nach viel Code aussehen, aber Sie müssen die Klassen DelayedDecorator und PolyArgDecoratorMeta nicht jeden Falls schreiben. Der einzige Code, den Sie persönlich schreiben müssen, sieht etwa wie folgt aus, was ziemlich kurz ist:

from PolyArgDecoratorMeta import PolyArgDecoratorMeta
import time
class Warten(metaclass=PolyArgDecoratorMeta):
 def __init__(i, func, delay = 2):
     i._func = func
     i._delay = delay

 def __call__(i, *args, **kwargs):
     time.sleep(i._delay)
     r = i._func(*args, **kwargs)
     return r

4voto

ZacBook Punkte 41

In meinem Fall habe ich beschlossen, dies über eine Einfach-Lambda zu lösen, um eine neue Dekoratorfunktion zu erstellen:

def finished_message(function, message="Fertig!"):

    def wrapper(*args, **kwargs):
        output = function(*args,**kwargs)
        print(message)
        return output

    return wrapper

@finished_message
def func():
    pass

my_finished_message = lambda f: finished_message(f, "Alles erledigt!")

@my_finished_message
def my_func():
    pass

if __name__ == '__main__':
    func()
    my_func()

Bei Ausführung wird Folgendes ausgegeben:

Fertig!
Alles erledigt!

Vielleicht nicht so erweiterbar wie andere Lösungen, aber hat für mich funktioniert.

0 Stimmen

Das funktioniert. Obwohl ja, dies macht es schwierig, den Wert auf den Dekorateur zu setzen.

4voto

Belhadjer Samir Punkte 1235

Der Dekorator mit Argumenten sollte eine Funktion zurückgeben, die eine Funktion entgegennimmt und eine andere Funktion zurückgibt. Das kannst du machen

def decorator_factory(argument):
    def decorator(function):
        def wrapper(*args, **kwargs):
            """
                etwas hinzufügen
            """
            return function(*args, **kwargs)
        return wrapper
    return decorator

oder du kannst `partial` aus dem Modul `functools` verwenden

def decorator(function=None, *, argument):
        if function is None :
            return partial(decorator, argument=argument)
        def wrapper(*args, **kwargs):
            """
                etwas hinzufügen
            """
            return function(*args, **kwargs)
        return wrapper

Bei der zweiten Option achte einfach darauf, die Argumente so zu übergeben:

@decorator(argument = 'args')
def func():
    pass

0 Stimmen

Die eleganteste Lösung IMHO, habe sie im Python 3 Metaprogramming Tutorial von David Beazley gefunden.

3voto

Evgeniy_Burdin Punkte 449

Es ist ein Dekorateur, der auf verschiedene Weisen aufgerufen werden kann (getestet in python3.7):

import functools

def my_decorator(*args_or_func, **decorator_kwargs):

    def _decorator(func):

        @functools.wraps(func)
        def wrapper(*args, **kwargs):

            if not args_or_func or callable(args_or_func[0]):
                # Hier können Sie Standardwerte für Positional Arguments festlegen
                decorator_args = ()
            else:
                decorator_args = args_or_func

            print(
                "Verfügbar innerhalb des Wrappers:",
                decorator_args, decorator_kwargs
            )

            # ...
            result = func(*args, **kwargs)
            # ...

            return result

        return wrapper

    return _decorator(args_or_func[0]) \
        if args_or_func and callable(args_or_func[0]) else _decorator

@my_decorator
def func_1(arg): print(arg)

func_1("test")
# Verfügbare innen des Wrappers: () {}
# test

@my_decorator()
def func_2(arg): print(arg)

func_2("test")
# Verfügbare innen des Wrappers: () {}
# test

@my_decorator("beliebiger Argument")
def func_3(arg): print(arg)

func_3("test")
# Verfügbare innen des Wrappers: ('beliebiger Argument',) {}
# test

@my_decorator("arg_1", 2, [3, 4, 5], kwarg_1=1, kwarg_2="2")
def func_4(arg): print(arg)

func_4("test")
# Verfügbare innen des Wrappers: ('arg_1', 2, [3, 4, 5]) {'kwarg_1': 1, 'kwarg_2': '2'}
# test

PS dank Benutzer @norok2 - https://stackoverflow.com/a/57268935/5353484

UPD Dekorateur zum Validieren von Argumenten und/oder Ergebnissen von Funktionen und Methoden einer Klasse gegen Annotationen. Kann in synchroner oder asynchroner Version verwendet werden: https://github.com/EvgeniyBurdin/valdec

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