J'ai un problème avec le transfert de la variable 'insurance_mode' par le décorateur. Je le ferais par la déclaration de décorateur suivante:
@execute_complete_reservation(True)
def test_booking_gta_object(self):
self.test_select_gta_object()
mais malheureusement, cette déclaration ne fonctionne pas. Peut-être qu'il y a peut-être un meilleur moyen de résoudre ce problème.
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
Vous voulez dire def test_booking_gta_object
, non? Quoi qu'il en soit, la syntaxe pour les décorateurs avec des arguments est un peu différente - le décorateur avec des arguments devrait retourner une fonction qui prendra une fonction et renverra une autre fonction. Donc, il devrait vraiment retourner un décorateur normal. Un peu déroutant, non? Ce que je veux dire est:
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
Ici vous pouvez en savoir plus sur le sujet - il est également possible de le mettre en œuvre en utilisant des objets appelables, ce qui est également expliqué ici.
Une façon de penser aux décorateurs avec des arguments est
@decorator
def foo(*args, **kwargs):
pass
se traduit par
foo = decorator(foo)
Donc, si le décorateur avait des arguments,
@decorator_with_args(arg)
def foo(*args, **kwargs):
pass
se traduit par
foo = decorator_with_args(arg)(foo)
decorator_with_args
est une fonction qui accepte un argument personnalisé et qui renvoie le décorateur réel (qui sera appliqué à la fonction décorée).
J'utilise un truc simple avec des partiels pour faciliter la tâche de mes décorateurs
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
Mettre à jour:
Ci-dessus, foo
devient real_decorator(foo)
Un effet de la décoration d’une fonction est que le nom foo
est remplacé par la déclaration du décorateur. foo
est "remplacé" par tout ce qui est retourné par real_decorator
. Dans ce cas, un nouvel objet de fonction.
Toutes les métadonnées de foo
sont écrasées, notamment docstring et nom de fonction.
>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>
functools.wraps nous donne une méthode pratique pour "lever" la docstring et le nom de la fonction retournée.
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)>
Je voudrais montrer une idée qui est IMHO assez élégant. La solution proposée par t.dubrownik montre un motif qui reste toujours le même: vous avez besoin de l’enveloppe à trois couches indépendamment de ce que fait le décorateur.
J'ai donc pensé que c'était un travail pour un méta-décorateur, c'est-à-dire un décorateur pour des décorateurs. En tant que fonction décoratrice, elle fonctionne en tant que fonction décoratrice classique avec des arguments:
def parametrized(dec):
def layer(*args, **kwargs):
def repl(f):
return dec(f, *args, **kwargs)
return repl
return layer
Ceci peut être appliqué à un décorateur régulier afin d'ajouter des paramètres. Ainsi, par exemple, supposons que le décorateur double le résultat d'une fonction:
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)
Avec @parametrized
nous pouvons construire un décorateur générique @multiply
ayant un paramètre
@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)
Classiquement, le premier paramètre de parametrized decorator est la fonction, tandis que les arguments restants correspondront au paramètre du décorateur paramétré.
Un exemple d'utilisation intéressant pourrait être un décorateur assertif:
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
Une note finale: ici, je n'utilise pas functools.wraps
pour les fonctions d'encapsulation, mais je recommanderais de l'utiliser tout le temps.
Voici une version légèrement modifiée de la réponse de t.dubrownik . Pourquoi?
Donc, utilisez @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
Je présume que votre problème est de passer des arguments à votre décorateur. C'est un peu délicat et pas simple.
Voici un exemple de la procédure à suivre:
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')
Impressions:
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
def decorator(argument):
def real_decorator(function):
def wrapper(*args):
for arg in args:
assert type(arg)==int,f'{arg} is not an interger'
result = function(*args)
result = result*argument
return result
return wrapper
return real_decorator
Usage du décorateur
@decorator(2)
def adder(*args):
sum=0
for i in args:
sum+=i
return sum
Puis le
adder(2,3)
produit
10
mais
adder('hi',3)
produit
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-143-242a8feb1cc4> in <module>
----> 1 adder('hi',3)
<ipython-input-140-d3420c248ebd> in wrapper(*args)
3 def wrapper(*args):
4 for arg in args:
----> 5 assert type(arg)==int,f'{arg} is not an interger'
6 result = function(*args)
7 result = result*argument
AssertionError: hi is not an interger
Dans mon cas, j'ai décidé de résoudre ce problème via un lambda sur une ligne pour créer une nouvelle fonction de décorateur:
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 __== '__main__':
func()
my_func()
Lorsqu'il est exécuté, ceci affiche:
Finished!
All Done!
Peut-être pas aussi extensible que d'autres solutions, mais a fonctionné pour moi.
définissez cette "fonction décoratrice" pour générer une fonction décoratrice personnalisée:
def decoratorize(FUN, **kw):
def foo(*args, **kws):
return FUN(*args, **kws, **kw)
return foo
utilisez-le de cette façon:
@decoratorize(FUN, arg1 = , arg2 = , ...)
def bar(...):
...
Ceci est un modèle pour un décorateur de fonctions qui ne nécessite pas ()
si aucun paramètre ne doit être donné:
import functools
def decorator(x_or_func=None, *decorator_args, **decorator_kws):
def _decorator(func):
@functools.wraps(func)
def wrapper(*args, **kws):
if 'x_or_func' not in locals() \
or callable(x_or_func) \
or x_or_func is None:
x = ... # <-- default `x` value
else:
x = x_or_func
return func(*args, **kws)
return wrapper
return _decorator(x_or_func) if callable(x_or_func) else _decorator
un exemple de ceci est donné ci-dessous:
def multiplying(factor_or_func=None):
def _decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if 'factor_or_func' not in locals() \
or callable(factor_or_func) \
or factor_or_func is None:
factor = 1
else:
factor = factor_or_func
return factor * func(*args, **kwargs)
return wrapper
return _decorator(factor_or_func) if callable(factor_or_func) 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(10)
def summing(x): return sum(x)
print(summing(range(10)))
# 450