Here's our solution for the day 29 exercise in the 30 Days of Python series. Make sure you try the exercise yourself before checking out the solution!
1) Make a decorator which calls a given function twice. You can assume the functions don't return anything important, but they may take arguments.
You can call your decorator whatever you like, but I'm going to go for double
here.
def double():
pass
There are few things we know we need to do right off the bat. We need our decorator to accept a function as an argument, and we need to return a reference to a function we define in the decorator's function body.
It's also a good idea to use wraps
to preserve the original function name and documentation.
from functools import wraps
def double(func):
@wraps(func)
def inner():
pass
return inner
Because our functions may accept arguments, we should define our inner
function to take any number of positional and keyword arguments using *args
, **kwargs**
. We should then pass on those arguments when calling the functions in inner
.
from functools import wraps
def double(func):
@wraps(func)
def inner(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return inner
2) Imagine you have a list called books
, which several functions in your application interact with. Write a decorator which causes your functions to run only if books
is not empty.
Let's start by setting up our usual decorator boilerplate. I'm also going to create a list called books
.
from functools import wraps
books = []
def requires_content(func):
@wraps(func)
def inner(*args, **kwargs):
pass
return inner
The actual logic here is fairly simple. We just need to check the truth value of books
inside inner
, and only call the function if the condition evaluates to True
.
from functools import wraps
books = []
def requires_content(func):
@wraps(func)
def inner(*args, **kwargs):
if books:
func(*args, **kwargs)
return inner
We should probably also return the return value of func
, just in case the function returns something.
from functools import wraps
books = []
def requires_content(func):
@wraps(func)
def inner(*args, **kwargs):
if books:
return func(*args, **kwargs)
return inner
3) Write a decorator called printer
which causes any decorated function to print their return values. If the return value of a given function is None
, printer
should do nothing.
This exercise is fairly similar to the previous one: we just need to have a condition based on the return value of the function, rather than some external variable.
Let's start with our boilerplate again.
from functools import wraps
def printer(func):
@wraps(func)
def inner(*args, **kwargs):
pass
return inner
The first step here is going to be actually calling the function inside inner
.
from functools import wraps
def printer(func):
@wraps(func)
def inner(*args, **kwargs):
return_value = func(*args, **kwargs)
return inner
Now we need to check the value we stored in return_value
.
It's really important that we don't just check the truth value of return_value
here, because there are plenty of other falsy values that might be returned by the function. For example, 0
, False
, or []
.
from functools import wraps
def printer(func):
@wraps(func)
def inner(*args, **kwargs):
return_value = func(*args, **kwargs)
if return_value is not None:
print(return_value)
return inner
Note that the idiom when checking for None
is to use is
and is not
, not ==
and !=
. This works because all instances of None
are actually the same value in memory.