Dekoratorzy z parametrami?

Mam problem z przeniesieniem zmiennej 'insurance_mode' przez dekoratora. Zrobiłbym to według następującego oświadczenia dekoratora:

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

Ale niestety, to stwierdzenie nie działa. Być może jest lepszy sposób na rozwiązanie tego problemu.

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
Author: martineau, 2011-05-08

6 answers

Masz na myśli def test_booking_gta_object, prawda? W każdym razie składnia dekoratorów z argumentami jest nieco inna - dekorator z argumentami powinien zwrócić funkcję, która przyjmie funkcję i zwróci inną funkcję. Więc to naprawdę powinno zwrócić normalny dekorator. Trochę zagmatwane, prawda? Chodzi mi o to:

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

Tutaj możesz przeczytać więcej na ten temat - możliwe jest również zaimplementowanie tego za pomocą obiektów, które można wywołać, i to również jest tam wyjaśnione.

 474
Author: t.dubrownik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-21 18:38:44

Jednym ze sposobów myślenia o dekoratorach z argumentami jest

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

Tłumaczy się na

foo = decorator(foo)

Więc jeśli dekorator miał argumenty,

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

Tłumaczy się na

foo = decorator_with_args(arg)(foo)

decorator_with_args jest funkcją, która przyjmuje Niestandardowy argument i która zwraca rzeczywisty dekorator (który zostanie zastosowany do dekorowanej funkcji).

Używam prostej sztuczki z częściami, aby moje dekoratory były łatwe

from functools import partial

def _pseudo_decor(fun, argument):
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %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:

Powyżej, foo staje się real_decorator(foo)

Jednym z efektów dekorowania funkcji jest to, że nazwa foo jest nadpisywana po deklaracji dekoratora. foo jest "overridden" przez to, co jest zwracane przez real_decorator. W tym przypadku nowy obiekt funkcji.

Wszystkie metadane foo są nadpisywane, w szczególności docstring i nazwa funkcji.

>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>

Functools.wraps daje nam wygodną metodę "podnoszenia" docstringu i nazwy do zwracanej funkcji.

from functools import partial, wraps

def _pseudo_decor(fun, argument):
    # magic sauce to lift the name and doc of the function
    @wraps(fun)
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

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

>>> print(bar)
<function __main__.bar(*args, **kwargs)>
 196
Author: srj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-28 02:31:06

Chciałbym pokazać pomysł, który jest IMHO całkiem elegancki. Rozwiązanie zaproponowane przez T. dubrownik pokazuje wzór, który jest zawsze taki sam: potrzebujesz trzywarstwowej owijki niezależnie od tego, co robi dekorator.

Więc pomyślałem, że to praca dla meta-dekoratora, czyli dekoratora dla dekoratorów. Ponieważ dekorator jest funkcją, w rzeczywistości działa jako zwykły dekorator z argumentami: {]}
def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

Można to zastosować do zwykłego dekoratora, aby dodać parametry. Więc dla przykład, powiedzmy, że mamy dekorator, który podwaja wynik funkcji:

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

@double
def function(a):
    return 10 + a

print function(3)    # Prints 26, namely 2 * (10 + 3)

Z @parametrized możemy zbudować ogólny @multiply dekorator o parametrze

@parametrized
def multiply(f, n):
    def aux(*xs, **kws):
        return n * f(*xs, **kws)
    return aux

@multiply(2)
def function(a):
    return 10 + a

print function(3)    # Prints 26

@multiply(3)
def function_again(a):
    return 10 + a

print function(3)          # Keeps printing 26
print function_again(3)    # Prints 39, namely 3 * (10 + 3)

Tradycyjnie pierwszym parametrem parametryzowanego dekoratora jest funkcja, podczas gdy pozostałe argumenty odpowiadają parametrowi parametryzowanego dekoratora.

Ciekawym przykładem użycia może być asertywny dekorator typu:

import itertools as it

@parametrized
def types(f, *types):
    def rep(*args):
        for a, t, n in zip(args, types, it.count()):
            if type(a) is not t:
                raise TypeError('Value %d has not type %s. %s instead' %
                    (n, t, type(a))
                )
        return f(*args)
    return rep

@types(str, int)  # arg1 is str, arg2 is int
def string_multiply(text, times):
    return text * times

print(string_multiply('hello', 3))    # Prints hellohellohello
print(string_multiply(3, 3))          # Fails miserably with TypeError

Ostatnia uwaga: tutaj nie jestem używanie functools.wraps dla funkcji wrappera, ale zalecałbym używanie go cały czas.

 59
Author: Dacav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 12:39:51

Oto nieco zmodyfikowana wersja odpowiedzi T. Dubrownika . Dlaczego?

  1. jako ogólny szablon, powinieneś zwrócić wartość zwracaną z oryginalnej funkcji.
  2. to zmienia nazwę funkcji, co może mieć wpływ na inne dekoratory / kod.

Więc użyj @functools.wraps():

from functools import wraps

def decorator(argument):
    def real_decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            retval = function(*args, **kwargs)
            more_funny_stuff()
            return retval
        return wrapper
    return real_decorator
 44
Author: Ross R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-22 10:44:05

Zakładam, że Twoim problemem jest przekazywanie argumentów dekoratorowi. Jest to trochę trudne i nie proste.

Oto przykład jak to zrobić:

class MyDec(object):
    def __init__(self,flag):
        self.flag = flag
    def __call__(self, original_func):
        decorator_self = self
        def wrappee( *args, **kwargs):
            print 'in decorator before wrapee with flag ',decorator_self.flag
            original_func(*args,**kwargs)
            print 'in decorator after wrapee with flag ',decorator_self.flag
        return wrappee

@MyDec('foo de fa fa')
def bar(a,b,c):
    print 'in bar',a,b,c

bar('x','y','z')

Druki:

in decorator before wrapee with flag  foo de fa fa
in bar x y z
in decorator after wrapee with flag  foo de fa fa

Więcej szczegółów znajdziesz w artykule Bruce ' a Eckela.

 35
Author: Ross Rogers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 12:37:27

W moim przypadku, postanowiłem rozwiązać to za pomocą jednej linii lambda, aby utworzyć nową funkcję dekoratora:

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

    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, "All Done!")

@my_finished_message
def my_func():
    pass

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

Po wykonaniu wyświetla się:

Finished!
All Done!

Może nie tak rozszerzalne jak inne rozwiązania, ale działa dla mnie.

 0
Author: ZacBook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-15 19:21:52