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
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.
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)>
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.
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?
- jako ogólny szablon, powinieneś zwrócić wartość zwracaną z oryginalnej funkcji.
- 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
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
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.
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