defmy_decorator(func): defwrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper
Something is happening before the function is called. Whee! Something is happening after the function is called.
1 2 3 4 5 6 7 8 9 10 11 12
from datetime import datetime
defnot_during_the_night(func): defwrapper(): if7 <= datetime.now().hour < 22: func() else: pass# Hush, the neighbors are asleep return wrapper defsay_whee(): print("Whee!")
Input:
1
say_whee()
Output:
1
Whee!
1
say_whee = not_during_the_night(say_whee)
Input:
Time is 16:00 when I am running the following code.
1
say_whee()
Output:
1
Whee!
Syntactic Sugar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
defmy_decorator(func): defwrapper(): print("Something is happening before the function is called.") func() print("Something is happening after the function is called.") return wrapper
@my_decorator defsay_whee(): print("Whee!")
# say_whee = my_decorator(say_whee)
say_whee()
1 2 3
Something is happening before the function is called. Whee! Something is happening after the function is called.
Return values from decorated functions don’t get returned by default unless the decorator allows it. In this lesson, you’ll see how to get return values out of decorated functions by making a small change to the decorator.
defdecorator(func): """Decorator Template.""" @functools.wraps(func) defwrapper_decorator(*args, **kwargs): # Do something before value = func(*args, **kwargs) # Do something after return value return wrapper_decorator
In this lesson, you’ll see how to use decorators to measure the time a function takes to execute and print the duration to the console.
By using decorator @timer, we didn’t add or modify any code in function waste_some_time. And not only we get the returned value from waste_some_time, but also can we get the runtime for waste_some_time.
Timer example is copied from Decorator Template
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import functools import time
deftimer(func): """Print the runtime of the decorated function""" @functools.wraps(func) defwrapper_timer(*args, **kwargs): # Do something before start_time = time.perf_counter() # 1 value = func(*args, **kwargs) # Do something after end_time = time.perf_counter() # 2 run_time = end_time - start_time # 3 print(f"Finished {func.__name__!r} in {run_time: .4f} secs") # wrapper version of the name return value return wrapper_timer
1 2 3 4
@timer defwaste_some_time(num_times): for _ inrange(num_times): sum([i**2for i inrange(10000)])
1
waste_some_time(1)
1
Finished 'waste_some_time' in 0.0039 secs
1
waste_some_time(999)
1
Finished 'waste_some_time' in 3.3956 secs
Debugging Code With Decorators
In this lesson, you’ll see how to create a decorator for debugging code. You’ll see how the @debug decorator prints out the arguments a function is called with as well as its return value every time the function is called.
Debug example is copied from Decorator Template
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import functools
defdebug(func): """"Print the function signature and return value.""" @functools.wraps(func) defwrapper_debug(*args, **kwargs): # Do something before args_repr = [repr(a) for a in args] # 1 kwargs_repr = [f"{k} = {v!r}"for k, v in kwargs.items()] # 2 signature = ", ".join(args_repr + kwargs_repr) # 3 print(f"Calling {func.__name__}({signature})") value = func(*args, **kwargs) # Do something after print(f"{func.__name__!r} returned {value!r}") # 4 return value return wrapper_debug
1 2 3 4 5 6
@debug defmake_greeting(name, age=None): if age isNone: returnf"Howdy {name}!" else: returnf"Whoa {name}! {age} already, you are growing up!"
1
make_greeting('Zacks')
1 2 3
Calling make_greeting('Zacks') 'make_greeting' returned 'Howdy Zacks!' 'Howdy Zacks!'
1
make_greeting('Zacks', 25)
1 2 3
Calling make_greeting('Zacks', 25) 'make_greeting' returned 'Whoa Zacks! 25 already, you are growing up!' 'Whoa Zacks! 25 already, you are growing up!'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
import functools import math
defdebug(func): """"Print the function signature and return value.""" @functools.wraps(func) defwrapper_debug(*args, **kwargs): # Do something before args_repr = [repr(a) for a in args] # 1 kwargs_repr = [f"{k} = {v!r}"for k, v in kwargs.items()] # 2 signature = ", ".join(args_repr + kwargs_repr) # 3 print(f"Calling {func.__name__}({signature})") value = func(*args, **kwargs) # Do something after print(f"{func.__name__!r} returned {value!r}") # 4 return value return wrapper_debug
1 2 3 4
math.factorial = debug(math.factorial)
defapproximate_e(terms=18): returnsum(1 / math.factorial(n) for n inrange(terms))
1 2 3 4 5 6 7 8 9 10 11 12
approximate_e(5) Calling factorial(0) 'factorial' returned 1 Calling factorial(1) 'factorial' returned 1 Calling factorial(2) 'factorial' returned 2 Calling factorial(3) 'factorial' returned 6 Calling factorial(4) 'factorial' returned 24 2.708333333333333
Slowing Down Code With Decorators
Decorators can be used to slow code down. Slowing down code can be useful when working with web services or when you want to rate limit a function. In this lesson, you’ll see how to create a @slow_down decorator that sleeps one second before it calls the decorated function.
It’s useful when you are visiting resources such as API. with some limitations.
Slowing Down example is copied from Decorator Template
1 2 3 4 5 6 7 8 9 10 11 12 13
import functools import time
defslow_down(func): """Sleep 1 second before calling the function""" @functools.wraps(func) defwrapper_slow_down(*args, **kwargs): # Do something before time.sleep(1) value = func(*args, **kwargs) # Do something after return value return wrapper_slow_down
The main benefit of registering Plugins with decorators is that you don’t have maintain a list of the functions you have created. You can easily know which plugins are exist.
1 2 3 4 5 6 7 8
import random
PLUGINS = dict()
defregister(func): """Register a function as a plug-in""" PLUGINS[func.__name__] = func return func