¿Cómo hacer una cadena de decoradores funcionales?

¿Cómo puedo hacer dos decoradores en Python que hagan lo siguiente?

@makebold @makeitalic def say(): return "Hello" 

… que debería devolver:

 "Hello" 

No estoy tratando de hacer HTML esta manera en una aplicación real, solo trato de entender cómo funcionan los decoradores y el encadenamiento de decoradores.

Echa un vistazo a la documentación para ver cómo trabajan los decoradores. Esto es lo que pediste:

 from functools import wraps def makebold(fn): @wraps(fn) def wrapped(*args, **kwargs): return "" + fn(*args, **kwargs) + "" return wrapped def makeitalic(fn): @wraps(fn) def wrapped(*args, **kwargs): return "" + fn(*args, **kwargs) + "" return wrapped @makebold @makeitalic def hello(): return "hello world" @makebold @makeitalic def log(s): return s print hello() # returns "hello world" print hello.__name__ # with functools.wraps() this returns "hello" print log('hello') # returns "hello" 

Si no te gustan las explicaciones largas, lee la respuesta de Paolo Bergantino .

Fundamentos del decorador

Las funciones de Python son objetos.

Para entender a los decoradores, primero debes entender que las funciones son objetos en Python. Esto tiene importantes consecuencias. Veamos por qué con un simple ejemplo:

 def shout(word="yes"): return word.capitalize()+"!" print(shout()) # outputs : 'Yes!' # As an object, you can assign the function to a variable like any other object scream = shout # Notice we don't use parentheses: we are not calling the function, # we are putting the function "shout" into the variable "scream". # It means you can then call "shout" from "scream": print(scream()) # outputs : 'Yes!' # More than that, it means you can remove the old name 'shout', # and the function will still be accessible from 'scream' del shout try: print(shout()) except NameError, e: print(e) #outputs: "name 'shout' is not defined" print(scream()) # outputs: 'Yes!' 

Mantén esto en mente. Vamos a regresar a él en breve.

Otra propiedad interesante de las funciones de Python es que se pueden definir dentro de otra función.

 def talk(): # You can define a function on the fly in "talk" ... def whisper(word="yes"): return word.lower()+"..." # ... and use it right away! print(whisper()) # You call "talk", that defines "whisper" EVERY TIME you call it, then # "whisper" is called in "talk". talk() # outputs: # "yes..." # But "whisper" DOES NOT EXIST outside "talk": try: print(whisper()) except NameError, e: print(e) #outputs : "name 'whisper' is not defined"* #Python's functions are objects 

Referencias de funciones

Está bien, todavía aquí? Ahora la parte divertida …

Has visto que las funciones son objetos. Por lo tanto, funciona:

  • se puede asignar a una variable
  • Se puede definir en otra función.

Eso significa que una función puede return otra función .

 def getTalk(kind="shout"): # We define functions on the fly def shout(word="yes"): return word.capitalize()+"!" def whisper(word="yes") : return word.lower()+"..."; # Then we return one of them if kind == "shout": # We don't use "()", we are not calling the function, # we are returning the function object return shout else: return whisper # How do you use this strange beast? # Get the function and assign it to a variable talk = getTalk() # You can see that "talk" is here a function object: print(talk) #outputs :  # The object is the one returned by the function: print(talk()) #outputs : Yes! # And you can even use it directly if you feel wild: print(getTalk("whisper")()) #outputs : yes... 

¡Hay más!

Si puede return una función, puede pasar una como parámetro:

 def doSomethingBefore(func): print("I do something before then I call the function you gave me") print(func()) doSomethingBefore(scream) #outputs: #I do something before then I call the function you gave me #Yes! 

Bueno, solo tienes todo lo necesario para entender a los decoradores. Verás, los decoradores son “envoltorios”, lo que significa que te permiten ejecutar código antes y después de la función que decoran sin modificar la función en sí.

Decoradores artesanales

Cómo lo harías manualmente:

 # A decorator is a function that expects ANOTHER function as parameter def my_shiny_new_decorator(a_function_to_decorate): # Inside, the decorator defines a function on the fly: the wrapper. # This function is going to be wrapped around the original function # so it can execute code before and after it. def the_wrapper_around_the_original_function(): # Put here the code you want to be executed BEFORE the original function is called print("Before the function runs") # Call the function here (using parentheses) a_function_to_decorate() # Put here the code you want to be executed AFTER the original function is called print("After the function runs") # At this point, "a_function_to_decorate" HAS NEVER BEEN EXECUTED. # We return the wrapper function we have just created. # The wrapper contains the function and the code to execute before and after. It's ready to use! return the_wrapper_around_the_original_function # Now imagine you create a function you don't want to ever touch again. def a_stand_alone_function(): print("I am a stand alone function, don't you dare modify me") a_stand_alone_function() #outputs: I am a stand alone function, don't you dare modify me # Well, you can decorate it to extend its behavior. # Just pass it to the decorator, it will wrap it dynamically in # any code you want and return you a new function ready to be used: a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function_decorated() #outputs: #Before the function runs #I am a stand alone function, don't you dare modify me #After the function runs 

Ahora, es probable que desees que cada vez que llames a_stand_alone_function , a_stand_alone_function_decorated sea ​​llamado en su lugar. Eso es fácil, solo sobrescriba a_stand_alone_function con la función devuelta por my_shiny_new_decorator :

 a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function() #outputs: #Before the function runs #I am a stand alone function, don't you dare modify me #After the function runs # That's EXACTLY what decorators do! 

Decoradores desmitificados

El ejemplo anterior, usando la syntax del decorador:

 @my_shiny_new_decorator def another_stand_alone_function(): print("Leave me alone") another_stand_alone_function() #outputs: #Before the function runs #Leave me alone #After the function runs 

Sí, eso es todo, es así de simple. @decorator es solo un acceso directo a:

 another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function) 

Los decoradores son solo una variante pythonica del patrón de diseño del decorador . Hay varios patrones de diseño clásicos integrados en Python para facilitar el desarrollo (como los iteradores).

Por supuesto, puedes acumular decoradores:

 def bread(func): def wrapper(): print("") func() print("<\______/>") return wrapper def ingredients(func): def wrapper(): print("#tomatoes#") func() print("~salad~") return wrapper def sandwich(food="--ham--"): print(food) sandwich() #outputs: --ham-- sandwich = bread(ingredients(sandwich)) sandwich() #outputs: # # #tomatoes# # --ham-- # ~salad~ #<\______/> 

Usando la syntax del decorador de Python:

 @bread @ingredients def sandwich(food="--ham--"): print(food) sandwich() #outputs: # # #tomatoes# # --ham-- # ~salad~ #<\______/> 

El orden que establezcas los decoradores importa:

 @ingredients @bread def strange_sandwich(food="--ham--"): print(food) strange_sandwich() #outputs: ##tomatoes# # # --ham-- #<\______/> # ~salad~ 

Ahora: para responder a la pregunta …

Como conclusión, puede ver fácilmente cómo responder a la pregunta:

 # The decorator to make it bold def makebold(fn): # The new function the decorator returns def wrapper(): # Insertion of some code before and after return "" + fn() + "" return wrapper # The decorator to make it italic def makeitalic(fn): # The new function the decorator returns def wrapper(): # Insertion of some code before and after return "" + fn() + "" return wrapper @makebold @makeitalic def say(): return "hello" print(say()) #outputs: hello # This is the exact equivalent to def say(): return "hello" say = makebold(makeitalic(say)) print(say()) #outputs: hello 

Ahora puede simplemente irse feliz o quemar un poco más su cerebro y ver los usos avanzados de los decoradores.


Llevando a los decoradores al siguiente nivel

Pasando argumentos a la función decorada.

 # It's not black magic, you just have to let the wrapper # pass the argument: def a_decorator_passing_arguments(function_to_decorate): def a_wrapper_accepting_arguments(arg1, arg2): print("I got args! Look: {0}, {1}".format(arg1, arg2)) function_to_decorate(arg1, arg2) return a_wrapper_accepting_arguments # Since when you are calling the function returned by the decorator, you are # calling the wrapper, passing arguments to the wrapper will let it pass them to # the decorated function @a_decorator_passing_arguments def print_full_name(first_name, last_name): print("My name is {0} {1}".format(first_name, last_name)) print_full_name("Peter", "Venkman") # outputs: #I got args! Look: Peter Venkman #My name is Peter Venkman 

Metodos de decoracion

Una cosa ingeniosa acerca de Python es que los métodos y las funciones son realmente lo mismo. La única diferencia es que los métodos esperan que su primer argumento sea una referencia al objeto actual ( self ).

¡Eso significa que puedes construir un decorador para métodos de la misma manera! Sólo recuerde tener en cuenta a self :

 def method_friendly_decorator(method_to_decorate): def wrapper(self, lie): lie = lie - 3 # very friendly, decrease age even more :-) return method_to_decorate(self, lie) return wrapper class Lucy(object): def __init__(self): self.age = 32 @method_friendly_decorator def sayYourAge(self, lie): print("I am {0}, what did you think?".format(self.age + lie)) l = Lucy() l.sayYourAge(-3) #outputs: I am 26, what did you think? 

Si está creando un decorador de propósito general, uno se aplicará a cualquier función o método, sin importar sus argumentos, entonces simplemente use *args, **kwargs :

 def a_decorator_passing_arbitrary_arguments(function_to_decorate): # The wrapper accepts any arguments def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs): print("Do I have args?:") print(args) print(kwargs) # Then you unpack the arguments, here *args, **kwargs # If you are not familiar with unpacking, check: # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/ function_to_decorate(*args, **kwargs) return a_wrapper_accepting_arbitrary_arguments @a_decorator_passing_arbitrary_arguments def function_with_no_argument(): print("Python is cool, no argument here.") function_with_no_argument() #outputs #Do I have args?: #() #{} #Python is cool, no argument here. @a_decorator_passing_arbitrary_arguments def function_with_arguments(a, b, c): print(a, b, c) function_with_arguments(1,2,3) #outputs #Do I have args?: #(1, 2, 3) #{} #1 2 3 @a_decorator_passing_arbitrary_arguments def function_with_named_arguments(a, b, c, platypus="Why not ?"): print("Do {0}, {1} and {2} like platypus? {3}".format(a, b, c, platypus)) function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!") #outputs #Do I have args ? : #('Bill', 'Linus', 'Steve') #{'platypus': 'Indeed!'} #Do Bill, Linus and Steve like platypus? Indeed! class Mary(object): def __init__(self): self.age = 31 @a_decorator_passing_arbitrary_arguments def sayYourAge(self, lie=-3): # You can now add a default value print("I am {0}, what did you think?".format(self.age + lie)) m = Mary() m.sayYourAge() #outputs # Do I have args?: #(<__main__.Mary object at 0xb7d303ac>,) #{} #I am 28, what did you think? 

Pasando argumentos al decorador.

Genial, ahora, ¿qué dirías acerca de pasarle argumentos al decorador?

Esto se puede torcer un poco, ya que un decorador debe aceptar una función como argumento. Por lo tanto, no puede pasar los argumentos de la función decorada directamente al decorador.

Antes de apresurarse a la solución, escribamos un pequeño recordatorio:

 # Decorators are ORDINARY functions def my_decorator(func): print("I am an ordinary function") def wrapper(): print("I am function returned by the decorator") func() return wrapper # Therefore, you can call it without any "@" def lazy_function(): print("zzzzzzzz") decorated_function = my_decorator(lazy_function) #outputs: I am an ordinary function # It outputs "I am an ordinary function", because that's just what you do: # calling a function. Nothing magic. @my_decorator def lazy_function(): print("zzzzzzzz") #outputs: I am an ordinary function 

Es exactamente lo mismo. Se my_decoratormy_decorator “. Entonces, cuando @my_decorator , le está diciendo a Python que llame a la función ‘etiquetada por la variable ” my_decorator “‘.

¡Esto es importante! La etiqueta que le dé puede apuntar directamente al decorador, o no .

Vayamos al mal. ☺

 def decorator_maker(): print("I make decorators! I am executed only once: " "when you make me create a decorator.") def my_decorator(func): print("I am a decorator! I am executed only when you decorate a function.") def wrapped(): print("I am the wrapper around the decorated function. " "I am called when you call the decorated function. " "As the wrapper, I return the RESULT of the decorated function.") return func() print("As the decorator, I return the wrapped function.") return wrapped print("As a decorator maker, I return a decorator") return my_decorator # Let's create a decorator. It's just a new function after all. new_decorator = decorator_maker() #outputs: #I make decorators! I am executed only once: when you make me create a decorator. #As a decorator maker, I return a decorator # Then we decorate the function def decorated_function(): print("I am the decorated function.") decorated_function = new_decorator(decorated_function) #outputs: #I am a decorator! I am executed only when you decorate a function. #As the decorator, I return the wrapped function # Let's call the function: decorated_function() #outputs: #I am the wrapper around the decorated function. I am called when you call the decorated function. #As the wrapper, I return the RESULT of the decorated function. #I am the decorated function. 

No hay sorpresa aquí.

Hagamos EXACTAMENTE lo mismo, pero omitamos todas las variables intermedias molestas:

 def decorated_function(): print("I am the decorated function.") decorated_function = decorator_maker()(decorated_function) #outputs: #I make decorators! I am executed only once: when you make me create a decorator. #As a decorator maker, I return a decorator #I am a decorator! I am executed only when you decorate a function. #As the decorator, I return the wrapped function. # Finally: decorated_function() #outputs: #I am the wrapper around the decorated function. I am called when you call the decorated function. #As the wrapper, I return the RESULT of the decorated function. #I am the decorated function. 

Hagámoslo aún más corto :

 @decorator_maker() def decorated_function(): print("I am the decorated function.") #outputs: #I make decorators! I am executed only once: when you make me create a decorator. #As a decorator maker, I return a decorator #I am a decorator! I am executed only when you decorate a function. #As the decorator, I return the wrapped function. #Eventually: decorated_function() #outputs: #I am the wrapper around the decorated function. I am called when you call the decorated function. #As the wrapper, I return the RESULT of the decorated function. #I am the decorated function. 

Oye, ¿viste eso? ¡Usamos una llamada a la función con la syntax ” @ “! 🙂

Así que, volvemos a los decoradores con argumentos. Si podemos usar funciones para generar el decorador sobre la marcha, podemos pasar argumentos a esa función, ¿verdad?

 def decorator_maker_with_arguments(decorator_arg1, decorator_arg2): print("I make decorators! And I accept arguments: {0}, {1}".format(decorator_arg1, decorator_arg2)) def my_decorator(func): # The ability to pass arguments here is a gift from closures. # If you are not comfortable with closures, you can assume it's ok, # or read: https://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python print("I am the decorator. Somehow you passed me arguments: {0}, {1}".format(decorator_arg1, decorator_arg2)) # Don't confuse decorator arguments and function arguments! def wrapped(function_arg1, function_arg2) : print("I am the wrapper around the decorated function.\n" "I can access all the variables\n" "\t- from the decorator: {0} {1}\n" "\t- from the function call: {2} {3}\n" "Then I can pass them to the decorated function" .format(decorator_arg1, decorator_arg2, function_arg1, function_arg2)) return func(function_arg1, function_arg2) return wrapped return my_decorator @decorator_maker_with_arguments("Leonard", "Sheldon") def decorated_function_with_arguments(function_arg1, function_arg2): print("I am the decorated function and only knows about my arguments: {0}" " {1}".format(function_arg1, function_arg2)) decorated_function_with_arguments("Rajesh", "Howard") #outputs: #I make decorators! And I accept arguments: Leonard Sheldon #I am the decorator. Somehow you passed me arguments: Leonard Sheldon #I am the wrapper around the decorated function. #I can access all the variables # - from the decorator: Leonard Sheldon # - from the function call: Rajesh Howard #Then I can pass them to the decorated function #I am the decorated function and only knows about my arguments: Rajesh Howard 

Aquí está: un decorador con argumentos. Los argumentos se pueden establecer como variable:

 c1 = "Penny" c2 = "Leslie" @decorator_maker_with_arguments("Leonard", c1) def decorated_function_with_arguments(function_arg1, function_arg2): print("I am the decorated function and only knows about my arguments:" " {0} {1}".format(function_arg1, function_arg2)) decorated_function_with_arguments(c2, "Howard") #outputs: #I make decorators! And I accept arguments: Leonard Penny #I am the decorator. Somehow you passed me arguments: Leonard Penny #I am the wrapper around the decorated function. #I can access all the variables # - from the decorator: Leonard Penny # - from the function call: Leslie Howard #Then I can pass them to the decorated function #I am the decorated function and only know about my arguments: Leslie Howard 

Como puede ver, puede pasar argumentos al decorador como a cualquier función usando este truco. Incluso puedes usar *args, **kwargs si lo deseas. Pero recuerda que los decoradores solo son llamados una vez . Justo cuando Python importa el script. No puedes establecer dinámicamente los argumentos después. Cuando haces “importar x”, la función ya está decorada , así que no puedes cambiar nada.


Practiquemos: decorar un decorador.

Bueno, como bono, te daré un fragmento para hacer que cualquier decorador acepte genéricamente cualquier argumento. Después de todo, para aceptar argumentos, creamos nuestro decorador usando otra función.

Envolvimos el decorador.

¿Algo más que vimos recientemente que envolvió la función?

¡Oh sí, decoradores!

Vamos a divertirnos y escribir un decorador para los decoradores:

 def decorator_with_args(decorator_to_enhance): """ This function is supposed to be used as a decorator. It must decorate an other function, that is intended to be used as a decorator. Take a cup of coffee. It will allow any decorator to accept an arbitrary number of arguments, saving you the headache to remember how to do that every time. """ # We use the same trick we did to pass arguments def decorator_maker(*args, **kwargs): # We create on the fly a decorator that accepts only a function # but keeps the passed arguments from the maker. def decorator_wrapper(func): # We return the result of the original decorator, which, after all, # IS JUST AN ORDINARY FUNCTION (which returns a function). # Only pitfall: the decorator must have this specific signature or it won't work: return decorator_to_enhance(func, *args, **kwargs) return decorator_wrapper return decorator_maker 

Se puede utilizar de la siguiente manera:

 # You create the function you will use as a decorator. And stick a decorator on it :-) # Don't forget, the signature is "decorator(func, *args, **kwargs)" @decorator_with_args def decorated_decorator(func, *args, **kwargs): def wrapper(function_arg1, function_arg2): print("Decorated with {0} {1}".format(args, kwargs)) return func(function_arg1, function_arg2) return wrapper # Then you decorate the functions you wish with your brand new decorated decorator. @decorated_decorator(42, 404, 1024) def decorated_function(function_arg1, function_arg2): print("Hello {0} {1}".format(function_arg1, function_arg2)) decorated_function("Universe and", "everything") #outputs: #Decorated with (42, 404, 1024) {} #Hello Universe and everything # Whoooot! 

Lo sé, la última vez que tuvo este sentimiento, fue después de escuchar a un tipo que decía: “antes de comprender la recursión, primero debe entender la recursión”. Pero ahora, ¿no te sientes bien de dominar esto?


Buenas prácticas: decoradores.

  • Los decoradores se introdujeron en Python 2.4, así que asegúrese de que su código se ejecute en> = 2.4.
  • Los decoradores ralentizan la llamada a la función. Mantenlo en mente.
  • No puedes des-decorar una función. (Hay trucos para crear decoradores que pueden eliminarse, pero nadie los usa). Por lo tanto, una vez que una función está decorada, está decorada para todo el código .
  • Los decoradores envuelven las funciones, lo que puede dificultar su depuración. (Esto mejora con Python> = 2.5; ver más abajo.)

El módulo functools fue introducido en Python 2.5. Incluye la función functools.wraps() , que copia el nombre, el módulo y la cadena de documentos de la función decorada en su envoltorio.

(Dato functools.wraps() : functools.wraps() es un decorador! ☺)

 # For debugging, the stacktrace prints you the function __name__ def foo(): print("foo") print(foo.__name__) #outputs: foo # With a decorator, it gets messy def bar(func): def wrapper(): print("bar") return func() return wrapper @bar def foo(): print("foo") print(foo.__name__) #outputs: wrapper # "functools" can help for that import functools def bar(func): # We say that "wrapper", is wrapping "func" # and the magic begins @functools.wraps(func) def wrapper(): print("bar") return func() return wrapper @bar def foo(): print("foo") print(foo.__name__) #outputs: foo 

¿Cómo pueden ser útiles los decoradores?

Ahora la gran pregunta: ¿Para qué puedo usar decoradores?

Parece fresco y poderoso, pero un ejemplo práctico sería genial. Bueno, hay 1000 posibilidades. Los usos clásicos son extender el comportamiento de una función desde una biblioteca externa (no puedes modificarla), o para la depuración (no quieres modificarla porque es temporal).

Puedes usarlas para extender varias funciones de una manera DRY, así:

 def benchmark(func): """ A decorator that prints the time a function takes to execute. """ import time def wrapper(*args, **kwargs): t = time.clock() res = func(*args, **kwargs) print("{0} {1}".format(func.__name__, time.clock()-t)) return res return wrapper def logging(func): """ A decorator that logs the activity of the script. (it actually just prints it, but it could be logging!) """ def wrapper(*args, **kwargs): res = func(*args, **kwargs) print("{0} {1} {2}".format(func.__name__, args, kwargs)) return res return wrapper def counter(func): """ A decorator that counts and prints the number of times a function has been executed """ def wrapper(*args, **kwargs): wrapper.count = wrapper.count + 1 res = func(*args, **kwargs) print("{0} has been used: {1}x".format(func.__name__, wrapper.count)) return res wrapper.count = 0 return wrapper @counter @benchmark @logging def reverse_string(string): return str(reversed(string)) print(reverse_string("Able was I ere I saw Elba")) print(reverse_string("A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!")) #outputs: #reverse_string ('Able was I ere I saw Elba',) {} #wrapper 0.0 #wrapper has been used: 1x #ablE was I ere I saw elbA #reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama!',) {} #wrapper 0.0 #wrapper has been used: 2x #!amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam A 

Por supuesto, lo bueno de los decoradores es que puede usarlos de inmediato en casi cualquier cosa sin tener que volver a escribir. SECO, dije:

 @counter @benchmark @logging def get_random_fututwig_quote(): from urllib import urlopen result = urlopen("http://subfusion.net/cgi-bin/quote.pl?quote=fututwig").read() try: value = result.split("


")[1].split("


")[0] return value.strip() except: return "No, I'm ... doesn't!" print(get_random_fututwig_quote()) print(get_random_fututwig_quote()) #outputs: #get_random_fututwig_quote () {} #wrapper 0.02 #wrapper has been used: 1x #The laws of science be a harsh mistress. #get_random_fututwig_quote () {} #wrapper 0.01 #wrapper has been used: 2x #Curse you, merciful Poseidon!

Python proporciona varios decoradores: property , staticmethod , etc.

  • Django utiliza decoradores para administrar el almacenamiento en caché y ver los permisos.
  • Torcido para falsificar llamadas de funciones asíncronas en línea.

Esto realmente es un gran patio de recreo.

Alternativamente, puede escribir una función de fábrica que devuelva un decorador que envuelva el valor de retorno de la función decorada en una etiqueta pasada a la función de fábrica. Por ejemplo:

 from functools import wraps def wrap_in_tag(tag): def factory(func): @wraps(func) def decorator(): return '<%(tag)s>%(rv)s' % ( {'tag': tag, 'rv': func()}) return decorator return factory 

Esto le permite escribir:

 @wrap_in_tag('b') @wrap_in_tag('i') def say(): return 'hello' 

o

 makebold = wrap_in_tag('b') makeitalic = wrap_in_tag('i') @makebold @makeitalic def say(): return 'hello' 

Personalmente habría escrito el decorador de manera algo diferente:

 from functools import wraps def wrap_in_tag(tag): def factory(func): @wraps(func) def decorator(val): return func('<%(tag)s>%(val)s' % {'tag': tag, 'val': val}) return decorator return factory 

que daría lugar a:

 @wrap_in_tag('b') @wrap_in_tag('i') def say(val): return val say('hello') 

No olvide la construcción para la cual la syntax del decorador es una abreviatura:

 say = wrap_in_tag('b')(wrap_in_tag('i')(say))) 

Parece que las otras personas ya te han dicho cómo resolver el problema. Espero que esto te ayude a entender qué son los decoradores.

Los decoradores son solo azúcar sintáctica.

Esta

 @decorator def func(): ... 

se expande a

 def func(): ... func = decorator(func) 

Y, por supuesto, también puede devolver las lambdas desde una función decoradora:

 def makebold(f): return lambda: "" + f() + "" def makeitalic(f): return lambda: "" + f() + "" @makebold @makeitalic def say(): return "Hello" print say() 

Los decoradores de Python agregan funcionalidad extra a otra función

Un decorador en cursiva podría ser como

 def makeitalic(fn): def newFunc(): return "" + fn() + "" return newFunc 

Tenga en cuenta que una función se define dentro de una función. Lo que básicamente hace es reemplazar una función con la recién definida. Por ejemplo, tengo esta clase

 class foo: def bar(self): print "hi" def foobar(self): print "hi again" 

Ahora diga, quiero que ambas funciones impriman “—” antes y después de que se terminen. Podría agregar una impresión “—” antes y después de cada statement de impresión. Pero como no me gusta repetirme, haré un decorador.

 def addDashes(fn): # notice it takes a function as an argument def newFunction(self): # define a new function print "---" fn(self) # call the original function print "---" return newFunction # Return the newly defined function - it will "replace" the original 

Así que ahora puedo cambiar mi clase a

 class foo: @addDashes def bar(self): print "hi" @addDashes def foobar(self): print "hi again" 

Para más información sobre decoradores, visite http://www.ibm.com/developerworks/linux/library/l-cpdecor.html

Podría hacer dos decoradores separados que hagan lo que usted quiere, como se ilustra directamente a continuación. Note the use of *args, **kwargs in the declaration of the wrapped() function which supports the decorated function having multiple arguments (which isn’t really necessary for the example say() function, but is included for generality).

For similar reasons, the functools.wraps decorator is used to change the meta attributes of the wrapped function to be those of the one being decorated. This makes error messages and embedded function documentation ( func.__doc__ ) be those of the decorated function instead of wrapped() ‘s.

 from functools import wraps def makebold(fn): @wraps(fn) def wrapped(*args, **kwargs): return "" + fn(*args, **kwargs) + "" return wrapped def makeitalic(fn): @wraps(fn) def wrapped(*args, **kwargs): return "" + fn(*args, **kwargs) + "" return wrapped @makebold @makeitalic def say(): return 'Hello' print(say()) # -> Hello 

Refinements

As you can see there’s a lot of duplicate code in these two decorators. Given this similarity it would be better for you to instead make a generic one that was actually a decorator factory —in other words, a decorator that makes other decorators. That way there would be less code repetition—and allow the DRY principle to be followed.

 def html_deco(tag): def decorator(fn): @wraps(fn) def wrapped(*args, **kwargs): return '<%s>' % tag + fn(*args, **kwargs) + '' % tag return wrapped return decorator @html_deco('b') @html_deco('i') def greet(whom=''): return 'Hello' + (' ' + whom) if whom else '' print(greet('world')) # -> Hello world 

To make the code more readable, you can assign a more descriptive name to the factory-generated decorators:

 makebold = html_deco('b') makeitalic = html_deco('i') @makebold @makeitalic def greet(whom=''): return 'Hello' + (' ' + whom) if whom else '' print(greet('world')) # -> Hello world 

or even combine them like this:

 makebolditalic = lambda fn: makebold(makeitalic(fn)) @makebolditalic def greet(whom=''): return 'Hello' + (' ' + whom) if whom else '' print(greet('world')) # -> Hello world 

Efficiency

While the above examples do all work, the code generated involves a fair amount of overhead in the form of extraneous function calls when multiple decorator are applied at once. This may not matter, depending the exact usage (which might be I/O-bound, for instance).

If speed of the decorated function is important, the overhead can be kept to a single extra function call by writing a slightly different decorator factory-function which implements adding all the tags at once, so it can generate code that avoids the addtional function calls incurred by using separate decorators for each tag.

This requires more code in the decorator itself, but this only runs when it’s being appled to function definitions, not later when they themselves are called. This also applies when creating more readable names by using lambda functions as previously illustrated. Sample:

 def multi_html_deco(*tags): start_tags, end_tags = [], [] for tag in tags: start_tags.append('<%s>' % tag) end_tags.append('' % tag) start_tags = ''.join(start_tags) end_tags = ''.join(reversed(end_tags)) def decorator(fn): @wraps(fn) def wrapped(*args, **kwargs): return start_tags + fn(*args, **kwargs) + end_tags return wrapped return decorator makebolditalic = multi_html_deco('b', 'i') @makebolditalic def greet(whom=''): return 'Hello' + (' ' + whom) if whom else '' print(greet('world')) # -> Hello world 

Another way of doing the same thing:

 class bol(object): def __init__(self, f): self.f = f def __call__(self): return "{}".format(self.f()) class ita(object): def __init__(self, f): self.f = f def __call__(self): return "{}".format(self.f()) @bol @ita def sayhi(): return 'hi' 

Or, more flexibly:

 class sty(object): def __init__(self, tag): self.tag = tag def __call__(self, f): def newf(): return "<{tag}>{res}".format(res=f(), tag=self.tag) return newf @sty('b') @sty('i') def sayhi(): return 'hi' 

How can I make two decorators in Python that would do the following?

You want the following function, when called:

 @makebold @makeitalic def say(): return "Hello" 

To return:

 Hello 

Simple solution

To most simply do this, make decorators that return lambdas (anonymous functions) that close over the function (closures) and call it:

 def makeitalic(fn): return lambda: '' + fn() + '' def makebold(fn): return lambda: '' + fn() + '' 

Now use them as desired:

 @makebold @makeitalic def say(): return 'Hello' 

y ahora:

 >>> say() 'Hello' 

Problems with the simple solution

But we seem to have nearly lost the original function.

 >>> say  at 0x4ACFA070> 

To find it, we’d need to dig into the closure of each lambda, one of which is buried in the other:

 >>> say.__closure__[0].cell_contents  at 0x4ACFA030> >>> say.__closure__[0].cell_contents.__closure__[0].cell_contents  

So if we put documentation on this function, or wanted to be able to decorate functions that take more than one argument, or we just wanted to know what function we were looking at in a debugging session, we need to do a bit more with our wrapper.

Full featured solution – overcoming most of these problems

We have the decorator wraps from the functools module in the standard library!

 from functools import wraps def makeitalic(fn): # must assign/update attributes from wrapped function to wrapper # __module__, __name__, __doc__, and __dict__ by default @wraps(fn) # explicitly give function whose attributes it is applying def wrapped(*args, **kwargs): return '' + fn(*args, **kwargs) + '' return wrapped def makebold(fn): @wraps(fn) def wrapped(*args, **kwargs): return '' + fn(*args, **kwargs) + '' return wrapped 

It is unfortunate that there’s still some boilerplate, but this is about as simple as we can make it.

In Python 3, you also get __qualname__ and __annotations__ assigned by default.

So now:

 @makebold @makeitalic def say(): """This function returns a bolded, italicized 'hello'""" return 'Hello' 

Y ahora:

 >>> say  >>> help(say) Help on function say in module __main__: say(*args, **kwargs) This function returns a bolded, italicized 'hello' 

Conclusión

So we see that wraps makes the wrapping function do almost everything except tell us exactly what the function takes as arguments.

There are other modules that may attempt to tackle the problem, but the solution is not yet in the standard library.

A decorator takes the function definition and creates a new function that executes this function and transforms the result.

 @deco def do(): ... 

is eqivarent to:

 do = deco(do) 

Ejemplo:

 def deco(func): def inner(letter): return func(letter).upper() #upper return inner 

Esta

 @deco def do(number): return chr(number) # number to letter 

is eqivalent to this def do2(number): return chr(number)

 do2 = deco(do2) 

65 <=> ‘a’

 print(do(65)) print(do2(65)) >>> B >>> B 

To understand the decorator, it is important to notice, that decorator created a new function do which is inner that executes func and transforms the result.

To explain decorator in a simpler way:

Con:

 @decor1 @decor2 def func(*args, **kwargs): pass 

When do:

 func(*args, **kwargs) 

You really do:

 decor1(decor2(func))(*args, **kwargs) 
 #decorator.py def makeHtmlTag(tag, *args, **kwds): def real_decorator(fn): css_class = " class='{0}'".format(kwds["css_class"]) \ if "css_class" in kwds else "" def wrapped(*args, **kwds): return "<"+tag+css_class+">" + fn(*args, **kwds) + "" return wrapped # return decorator dont call it return real_decorator @makeHtmlTag(tag="b", css_class="bold_css") @makeHtmlTag(tag="i", css_class="italic_css") def hello(): return "hello world" print hello() 

You can also write decorator in Class

 #class.py class makeHtmlTagClass(object): def __init__(self, tag, css_class=""): self._tag = tag self._css_class = " class='{0}'".format(css_class) \ if css_class != "" else "" def __call__(self, fn): def wrapped(*args, **kwargs): return "<" + self._tag + self._css_class+">" \ + fn(*args, **kwargs) + "" return wrapped @makeHtmlTagClass(tag="b", css_class="bold_css") @makeHtmlTagClass(tag="i", css_class="italic_css") def hello(name): return "Hello, {}".format(name) print hello("Your name") 

Here is a simple example of chaining decorators. Note the last line – it shows what is going on under the covers.

 ############################################################ # # decorators # ############################################################ def bold(fn): def decorate(): # surround with bold tags before calling original function return "" + fn() + "" return decorate def uk(fn): def decorate(): # swap month and day fields = fn().split('/') date = fields[1] + "/" + fields[0] + "/" + fields[2] return date return decorate import datetime def getDate(): now = datetime.datetime.now() return "%d/%d/%d" % (now.day, now.month, now.year) @bold def getBoldDate(): return getDate() @uk def getUkDate(): return getDate() @bold @uk def getBoldUkDate(): return getDate() print getDate() print getBoldDate() print getUkDate() print getBoldUkDate() # what is happening under the covers print bold(uk(getDate))() 

The output looks like:

 17/6/2013 17/6/2013 6/17/2013 6/17/2013 6/17/2013 

Speaking of the counter example – as given above, the counter will be shared between all functions that use the decorator:

 def counter(func): def wrapped(*args, **kws): print 'Called #%i' % wrapped.count wrapped.count += 1 return func(*args, **kws) wrapped.count = 0 return wrapped 

That way, your decorator can be reused for different functions (or used to decorate the same function multiple times: func_counter1 = counter(func); func_counter2 = counter(func) ), and the counter variable will remain private to each.

This answer has long been answered, but I thought I would share my Decorator class which makes writing new decorators easy and compact.

 from abc import ABCMeta, abstractclassmethod class Decorator(metaclass=ABCMeta): """ Acts as a base class for all decorators """ def __init__(self): self.method = None def __call__(self, method): self.method = method return self.call @abstractclassmethod def call(self, *args, **kwargs): return self.method(*args, **kwargs) 

For one I think this makes the behavior of decorators very clear, but it also makes it easy to define new decorators very concisely. For the example listed above, you could then solve it as:

 class MakeBold(Decorator): def call(): return "" + self.method() + "" class MakeItalic(Decorator): def call(): return "" + self.method() + "" @MakeBold() @MakeItalic() def say(): return "Hello" 

You could also use it to do more complex tasks, like for instance a decorator which automatically makes the function get applied recursively to all arguments in an iterator:

 class ApplyRecursive(Decorator): def __init__(self, *types): super().__init__() if not len(types): types = (dict, list, tuple, set) self._types = types def call(self, arg): if dict in self._types and isinstance(arg, dict): return {key: self.call(value) for key, value in arg.items()} if set in self._types and isinstance(arg, set): return set(self.call(value) for value in arg) if tuple in self._types and isinstance(arg, tuple): return tuple(self.call(value) for value in arg) if list in self._types and isinstance(arg, list): return list(self.call(value) for value in arg) return self.method(arg) @ApplyRecursive(tuple, set, dict) def double(arg): return 2*arg print(double(1)) print(double({'a': 1, 'b': 2})) print(double({1, 2, 3})) print(double((1, 2, 3, 4))) print(double([1, 2, 3, 4, 5])) 

Que imprime:

 2 {'a': 2, 'b': 4} {2, 4, 6} (2, 4, 6, 8) [1, 2, 3, 4, 5, 1, 2, 3, 4, 5] 

Notice that this example didn’t include the list type in the instantiation of the decorator, so in the final print statement the method gets applied to the list itself, not the elements of the list.

Decorate functions with different number of arguments:

 def frame_tests(fn): def wrapper(*args): print "\nStart: %s" %(fn.__name__) fn(*args) print "End: %s\n" %(fn.__name__) return wrapper @frame_tests def test_fn1(): print "This is only a test!" @frame_tests def test_fn2(s1): print "This is only a test! %s" %(s1) @frame_tests def test_fn3(s1, s2): print "This is only a test! %s %s" %(s1, s2) if __name__ == "__main__": test_fn1() test_fn2('OK!') test_fn3('OK!', 'Just a test!') 

Resultado:

 Start: test_fn1 This is only a test! End: test_fn1 Start: test_fn2 This is only a test! OK! End: test_fn2 Start: test_fn3 This is only a test! OK! Just a test! End: test_fn3 

Paolo Bergantino’s answer has the great advantage of only using the stdlib, and works for this simple example where there are no decorator arguments nor decorated function arguments.

However it has 3 major limitations if you want to tackle more general cases:

  • as already noted in several answers, you can not easily modify the code to add optional decorator arguments . For example creating a makestyle(style='bold') decorator is non-trivial.
  • besides, wrappers created with @functools.wraps do not preserve the signature , so if bad arguments are provided they will start executing, and might raise a different kind of error than the usual TypeError .
  • finally, it is quite difficult in wrappers created with @functools.wraps to access an argument based on its name . Indeed the argument can appear in *args , in **kwargs , or may not appear at all (if it is optional).

I wrote decopatch to solve the first issue, and wrote makefun.wraps to solve the other two. Note that makefun leverages the same trick than the famous decorator lib.

This is how you would create a decorator with arguments, returning truly signature-preserving wrappers:

 from decopatch import function_decorator, DECORATED from makefun import wraps @function_decorator def makestyle(st='b', fn=DECORATED): open_tag = "<%s>" % st close_tag = "" % st @wraps(fn) def wrapped(*args, **kwargs): return open_tag + fn(*args, **kwargs) + close_tag return wrapped 

decopatch provides you with two other development styles that hide or show the various python concepts, depending on your preferences. The most compact style is the following:

 from decopatch import function_decorator, WRAPPED, F_ARGS, F_KWARGS @function_decorator def makestyle(st='b', fn=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS): open_tag = "<%s>" % st close_tag = "" % st return open_tag + fn(*f_args, **f_kwargs) + close_tag 

In both cases you can check that the decorator works as expected:

 @makestyle @makestyle('i') def hello(who): return "hello %s" % who assert hello('world') == 'hello world' 

Please refer to the documentation for details.