# 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 : <function shout at 0xb7ea817c>
# 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...
What’s more, if you can return a function, you can pass one as a parameter:
1 2 3 4 5 6 7 8
defdoSomethingBefore(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!
Definition
Decorators are:
“wrappers” - which means that they let you execute code before and after the function they decorate without modifying the function itself
You cannot un-decorate a function. (There are hacks to create decorators that can be removed, but nobody uses them.) So once a function is decorated, it’s decorated for all the code.
Decorators wrap functions, which can make them hard to debug. (This gets better from Python >= 2.5 by having functools module which includes the function functools.wraps() that copies the name, module, and docstring of the decorated function to its wrapper.)
Why decorators?
Classic uses are extending a function behavior from an external lib (you can’t modify it), or for debugging (you don’t want to modify it because it’s temporary).
You can use them to extend several functions in a DRY’s way, like so:
defbenchmark(func): """ A decorator that prints the time a function takes to execute. """ import time defwrapper(*args, **kwargs): t = time.clock() res = func(*args, **kwargs) print("{0} {1}".format(func.__name__, time.clock()-t)) return res return wrapper
deflogging(func): """ A decorator that logs the activity of the script. (it actually just prints it, but it could be logging!) """ defwrapper(*args, **kwargs): res = func(*args, **kwargs) print("{0} {1} {2}".format(func.__name__, args, kwargs)) return res return wrapper
defcounter(func): """ A decorator that counts and prints the number of times a function has been executed """ defwrapper(*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
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
Of course the good thing with decorators is that you can use them right away on almost anything without rewriting. DRY, I said:
#outputs: #get_random_futurama_quote () {} #wrapper 0.02 #wrapper has been used: 1x #The laws of science be a harsh mistress. #get_random_futurama_quote () {} #wrapper 0.01 #wrapper has been used: 2x #Curse you, merciful Poseidon!
Python itself provides several decorators: property, staticmethod, etc.
Django uses decorators to manage caching and view permissions.
Twisted to fake inlining asynchronous functions calls.
This really is a large playground.
Handcrafted Decorators vs Decorator Syntax in Python
# A decorator is a function that expects ANOTHER function as parameter defmy_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. defthe_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. defa_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
Now, you probably want that every time you call a_stand_alone_function, a_stand_alone_function_decorated is called instead. That’s easy, just overwrite a_stand_alone_function with the function returned by my_shiny_new_decorator:
1 2 3 4 5 6 7 8
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!
The previous example, using the decorator syntax in Python:
1 2 3 4 5 6 7 8 9
@my_shiny_new_decorator defanother_stand_alone_function(): print("Leave me alone")
another_stand_alone_function() #outputs: #Before the function runs #Leave me alone #After the function runs
# 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 defprint_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
Decorating methods One nifty thing about Python is that methods and functions are really the same. The only difference is that methods expect that their first argument is a reference to the current object (self).
That means you can build a decorator for methods the same way! Just remember to take self into consideration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
defmethod_friendly_decorator(method_to_decorate): defwrapper(self, lie): lie = lie - 3# very friendly, decrease age even more :-) return method_to_decorate(self, lie) return wrapper
classLucy(object):
def__init__(self): self.age = 32
@method_friendly_decorator defsayYourAge(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?
If you’re making general-purpose decorator–one you’ll apply to any function or method, no matter its arguments–then just use *args, **kwargs:
defa_decorator_passing_arbitrary_arguments(function_to_decorate): # The wrapper accepts any arguments defa_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 deffunction_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 deffunction_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 deffunction_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!
classMary(object):
def__init__(self): self.age = 31
@a_decorator_passing_arbitrary_arguments defsayYourAge(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?