¿Decoradores con parámetros?

12 minutos de lectura

¿Decoradores con parametros
falek.marcin

Tengo un problema con la transferencia de la variable ‘insurance_mode’ por parte del decorador. Lo haría con la siguiente declaración del decorador:

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

pero desafortunadamente, esta declaración no funciona. Tal vez tal vez haya una mejor manera de resolver este problema.

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

  • Su ejemplo no es sintácticamente válido. execute_complete_reservation toma dos parámetros, pero le estás pasando uno. Los decoradores son solo azúcar sintáctico para envolver funciones dentro de otras funciones. Ver docs.python.org/reference/compound_stmts.html#function para la documentación completa.

    –Brian Clapper

    8 mayo 2011 a las 17:50


¿Decoradores con parametros
dubrownik

La sintaxis para los decoradores con argumentos es un poco diferente: el decorador con argumentos debería devolver una función que tomar una función y devolver otra función. Así que realmente debería devolver un decorador normal. Un poco confuso, ¿verdad? Lo que quiero decir es:

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

Aquí puede leer más sobre el tema; también es posible implementar esto usando objetos invocables y eso también se explica allí.

  • Me pregunto por qué GVR no lo implementó al pasar los parámetros como argumentos de decorador posteriores después de ‘función’. ‘Oye amigo, escuché que te gustan los cierres…’ etcétera.

    – Michel Muller

    08/04/2014 a las 16:22

  • > ¿La función sería el primer argumento o último? Obviamente primero, ya que los parámetros son una lista de parámetros de longitud variable. > También es extraño que “llames” a la función con una firma diferente a la de la definición. Como usted señala, en realidad encajaría bastante bien: es bastante análogo a cómo se llama un método de clase. Para que quede más claro, podría tener algo como la convención decorator(self_func, param1, …). Pero tenga en cuenta: no estoy abogando por ningún cambio aquí, Python está demasiado avanzado para eso y podemos ver cómo han funcionado los cambios importantes.

    – Michel Muller

    19 de febrero de 2015 a las 1:07


  • olvidaste MUY ÚTILES functools.wraps para decorar envoltorios 🙂

    – par de enchufes

    13 de agosto de 2015 a las 21:19


  • Se olvidó del retorno al llamar a la función, es decir return function(*args, **kwargs)

    – formiaczek

    1 de diciembre de 2015 a las 17:09


  • Tal vez obvio, pero por si acaso: necesitas usar este decorador como @decorator() y no solo @decoratorincluso si solo tiene argumentos opcionales.

    –Patrick Mevzek

    04/12/2017 a las 20:25

1646974212 46 ¿Decoradores con parametros
Srj

Editar : para una comprensión profunda del modelo mental de los decoradores, eche un vistazo a esta Impresionante Pycon Talk. bien vale la pena los 30 minutos.

Una forma de pensar acerca de los decoradores con argumentos es

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

se traduce a

foo = decorator(foo)

Entonces, si el decorador tenía argumentos,

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

se traduce a

foo = decorator_with_args(arg)(foo)

decorator_with_args es una función que acepta un argumento personalizado y que devuelve el decorador real (que se aplicará a la función decorada).

Uso un truco simple con parciales para que mis decoradores sean fáciles

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

Actualizar:

Encima, foo se convierte real_decorator(foo)

Un efecto de decorar una función es que el nombre foo se anula con la declaración del decorador. foo es “anulado” por lo que sea devuelto por real_decorator. En este caso, un nuevo objeto de función.

Todo fooLos metadatos de se anulan, en particular la cadena de documentación y el nombre de la función.

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

functools.envolturas nos brinda un método conveniente para “levantar” la cadena de documentación y el nombre a la función devuelta.

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):
        # pre function execution stuff here, for eg.
        print("decorator argument is %s" % str(argument))
        returned_value =  fun(*args, **kwargs)
        # post execution stuff here, for eg.
        print("returned value is %s" % returned_value)
        return returned_value

    return ret_fun

real_decorator1 = partial(_pseudo_decor, argument="some_arg")
real_decorator2 = partial(_pseudo_decor, argument="some_other_arg")

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

>>> print(bar)
<function __main__.bar(*args, **kwargs)>

>>> bar(1,2,3, k="v", x="z")
decorator argument is some_arg
returned value is None

  • Su respuesta explicó perfectamente la ortogonalidad inherente del decorador, gracias.

    – zsf222

    9 de diciembre de 2017 a las 15:58

  • @Mr_and_Mrs_D, actualicé la publicación con un ejemplo con functool.wraps. Agregarlo en el ejemplo puede confundir aún más a los lectores.

    – Srj

    28 de agosto de 2018 a las 2:31

  • Qué es arg ¿¡aquí!?

    – Stefan Falk

    25 de septiembre de 2018 a las 10:22

  • ¿Cómo pasará el argumento pasado a bar al argumento de real_decorator ?

    -Chang Zhao

    1 de noviembre de 2018 a las 2:56

  • ¡La mejor explicación que he visto!

    – Cho

    25 de julio de 2019 a las 6:41

1646974213 382 ¿Decoradores con parametros
Dacav

Me gustaría mostrar una idea que en mi humilde opinión es bastante elegante. La solución propuesta por t.dubrownik muestra un patrón que siempre es el mismo: necesitas el envoltorio de tres capas independientemente de lo que haga el decorador.

Entonces pensé que este es un trabajo para un metadecorador, es decir, un decorador para decoradores. Como un decorador es una función, en realidad funciona como un decorador regular con argumentos:

def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

Esto se puede aplicar a un decorador regular para agregar parámetros. Entonces, por ejemplo, digamos que tenemos el decorador que duplica el resultado de una función:

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)

Con @parametrized podemos construir un genérico @multiply decorador que tiene un parámetro

@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)

Convencionalmente, el primer parámetro de un parametrizado decorador es la función, mientras que el resto de argumentos corresponderán al parámetro del decorador parametrizado.

Un ejemplo de uso interesante podría ser un decorador asertivo con seguridad de tipos:

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

Una nota final: aquí no estoy usando functools.wraps para las funciones de contenedor, pero recomendaría usarlo todo el tiempo.

  • No usé esto exactamente, pero me ayudó a entender el concepto 🙂 ¡Gracias!

    – mouckatron

    10 oct 2017 a las 22:04

  • Intenté esto y tuve algunos problemas.

    – Jeff

    14/10/2017 a las 14:53


  • @Jeff, ¿podrías compartir con nosotros el tipo de problemas que tuviste?

    – Dacav

    14/10/2017 a las 16:49

  • Lo tenía vinculado en mi pregunta, y lo descubrí… Necesitaba llamar @wraps en el mio para mi caso particular.

    – Jeff

    14/10/2017 a las 19:33

  • Oh chico, perdí un día entero en esto. Afortunadamente, encontré esta respuesta (que, por cierto, podría ser la mejor respuesta jamás creada en todo Internet). Ellos también usan su @parametrized truco. El problema que tuve fue que olvidé el @ sintaxis es igual a llamadas reales (de alguna manera lo sabía y no lo sabía al mismo tiempo, como puede deducir de mi pregunta). Así que si quieres traducir @ sintaxis en llamadas mundanas para ver cómo funciona, es mejor que primero lo comente temporalmente o terminará llamándolo dos veces y obteniendo resultados mumbojumbo

    – z33k

    13/03/2018 a las 14:50


¿Decoradores con parametros
Ross R.

Aquí hay una versión ligeramente modificada de la respuesta de t.dubrownik. ¿Por qué?

  1. Como plantilla general, debe devolver el valor de retorno de la función original.
  2. Esto cambia el nombre de la función, lo que podría afectar a otros decoradores/código.

Así que usa @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

1646974214 124 ¿Decoradores con parametros
ross rogers

Supongo que su problema es pasar argumentos a su decorador. Esto es un poco complicado y no es sencillo.

He aquí un ejemplo de cómo hacer esto:

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')

Huellas dactilares:

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

Consulte el artículo de Bruce Eckel para obtener más detalles.

  • Cuidado con las clases de decorador. No funcionan en métodos a menos que reinventes manualmente la lógica de los descriptores de métodos de instancia.

    usuario395760

    8 mayo 2011 a las 18:01

  • Delnan, ¿quieres dar más detalles? Solo he tenido que usar este patrón una vez, por lo que aún no me he topado con ninguna de las trampas.

    –Ross Rogers

    8 de mayo de 2011 a las 18:04


  • @RossRogers Supongo que @delnan se refiere a cosas como __name__ que no tendrá una instancia de la clase decorador?

    – jamesc

    13 de enero de 2014 a las 17:18

  • @jamesc Eso también, aunque eso es relativamente fácil de resolver. El caso específico al que me refería era class Foo: @MyDec(...) def method(self, ...): blah que no funciona porque Foo().method no será un método enlazado y no pasará self automáticamente. Esto también se puede arreglar, haciendo MyDec un descriptor y creando métodos enlazados en __get__, pero es más complicado y mucho menos obvio. Al final, las clases de decorador no son tan convenientes como parecen.

    usuario395760

    13 de enero de 2014 a las 21:49


  • @delnan Me gustaría ver esta advertencia destacada. Lo estoy acertando y estoy interesado en ver una solución que SÍ funcione (aunque sea más complicado y menos obvio).

    – HaPsantran

    13 de marzo de 2016 a las 6:42

1646974214 289 ¿Decoradores con parametros
sha de mierda

¡Escribir un decorador que funcione con y sin parámetros es un desafío porque Python espera un comportamiento completamente diferente en estos dos casos! Muchas respuestas han intentado solucionar esto y, a continuación, hay una mejora de la respuesta de @ norok2. Específicamente, esta variación elimina el uso de locals().

Siguiendo el mismo ejemplo dado por @norok2:

import functools

def multiplying(f_py=None, factor=1):
    assert callable(f_py) or f_py is None
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(f_py) if callable(f_py) 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(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

Juega con este código.

El problema es que el usuario debe proporcionar pares de parámetros clave, valor en lugar de parámetros posicionales y el primer parámetro está reservado.

  • Cuidado con las clases de decorador. No funcionan en métodos a menos que reinventes manualmente la lógica de los descriptores de métodos de instancia.

    usuario395760

    8 mayo 2011 a las 18:01

  • Delnan, ¿quieres dar más detalles? Solo he tenido que usar este patrón una vez, por lo que aún no me he topado con ninguna de las trampas.

    –Ross Rogers

    8 de mayo de 2011 a las 18:04


  • @RossRogers Supongo que @delnan se refiere a cosas como __name__ que no tendrá una instancia de la clase decorador?

    – jamesc

    13 de enero de 2014 a las 17:18

  • @jamesc Eso también, aunque eso es relativamente fácil de resolver. El caso específico al que me refería era class Foo: @MyDec(...) def method(self, ...): blah que no funciona porque Foo().method no será un método enlazado y no pasará self automáticamente. Esto también se puede arreglar, haciendo MyDec un descriptor y creando métodos enlazados en __get__, pero es más complicado y mucho menos obvio. Al final, las clases de decorador no son tan convenientes como parecen.

    usuario395760

    13 de enero de 2014 a las 21:49


  • @delnan Me gustaría ver esta advertencia destacada. Lo estoy acertando y estoy interesado en ver una solución que SÍ funcione (aunque sea más complicado y menos obvio).

    – HaPsantran

    13 de marzo de 2016 a las 6:42

open en Python no crea un archivo si no
Gajendra D Ambi

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

Uso del decorador.

@decorator(2)
def adder(*args):
    sum=0
    for i in args:
        sum+=i
    return sum

Entonces el

adder(2,3)

produce

10

pero

adder('hi',3)

produce

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

  • De todas las publicaciones aquí, esta respuesta resultó ser la más útil para mi comprensión de cómo se pasa y maneja el argumento.

    – Milo Persic

    22 de noviembre de 2020 a las 21:12

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad