web-dev-qa-db-fra.com

Décorateurs avec des paramètres?

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
299
falek.marcin

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.

527
t.dubrownik

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)>
232
srj

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.

68
Dacav

Voici une version légèrement modifiée de la réponse de t.dubrownik . Pourquoi?

  1. En tant que modèle général, vous devez renvoyer la valeur de retour de la fonction d'origine.
  2. Cela change le nom de la fonction, ce qui pourrait affecter d’autres décorateurs/codes.

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
53
Ross R

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

Voir l'article de Bruce Eckel pour plus de détails.

35
Ross Rogers
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
2
Gajendra D Ambi

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.

1
ZacBook

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(...):
        ...
0
chen.wq

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
0
norok2