Welcome to part 18 of the intermediate Python programming tutorial series. In this tutorial, we are going to be discussing decorators.
Decorators are a way for us to "wrap" a function inside another function, without actually hard-coding it to be like this every time. An example of this is Flask. When you go to create a new page, you simply define a new function, dictate what is returned, and then you use a decorator to give it an actual path, like:
@app.route('/contact/')
def contact():
return render_template("contact.html")
All we had to do was describe what the page will do, then, to actually allow a browser to access it via a URL, and to handle actually returning data and all of that, we just use a decorator that comes with all of these things already.
We can use decorators on functions or methods as we see fit. Let's consider a simple example. Let's say we're writing a function that returns a brand new GPU.
def new_gpu():
return 'a new Tesla P100 GPU!'
If we do a print(new_gpu()), we just get a new Tesla P100 GPU!
Right now, the function returns our new GPU, but it's actually a gift, so let's wrap it. To do this, we need to define our wrapping decoration:
def add_wrapping(item):
def wrapped_item():
return 'a wrapped up box of {}'.format(str(item()))
return wrapped_item
The add_wrapping is going to be our decorator function, which takes a single parameter, which is item. Whatever you want to actually wrap will go in here. When we wrap something, this just simply happens. Thus, when we "decorate" the new_gpu function, that new_gpu function will be the item. Then, we have a new embedded function here that actually wraps the item (wrapped_item). This function returns the wrapped version of the return of the item that we passed. Then, back in the add_wrapping function, we just return the wrapped_item. Now, to wrap something, we can do: @add_wrapping above it, like so:
@add_wrapping
def new_gpu():
return 'a new Tesla P100 GPU!'
All together now:
def add_wrapping(item):
def wrapped_item():
return 'a wrapped up box of {}'.format(str(item()))
return wrapped_item
@add_wrapping
def new_gpu():
return 'a new Tesla P100 GPU!'
print(new_gpu())
Output:
a wrapped up box of a new Tesla P100 GPU!
Got a bicycle?!
def new_bicycle():
return 'a new bicycle'
We can easily wrap that too:
@add_wrapping
def new_bicycle():
return 'a new bicycle'
print(new_bicycle())
Output:
a wrapped up box of a new bicycle
What if we...
@add_wrapping
@add_wrapping
def new_gpu():
return 'a new Tesla P100 GPU!'
You can chain decorators like this, and what you get is what you'd expect:
a wrapped up box of a wrapped up box of a new Tesla P100 GPU!
A few notes to make here, however. When we wrap a function, we're essentially overwriting it with new information, and we're losing some information. For example, with our gpu, we could reference its __name__:
print(new_gpu.__name__)
Which gives us: wrapped_item
That may be what we wanted, or it might actually not. We may actually want to keep that original information instead. We can do:
from functools import wraps
Then use @wraps over the wrapped item:
def add_wrapping(item):
@wraps(item)
def wrapped_item():
return 'a wrapped up box of {}'.format(str(item()))
return wrapped_item
Then, if we do: print(new_gpu.__name__), we get new_gpu.
What if we want to pass arguments, like we do with Flask or Django?
We can further nest the decorator:
def add_wrapping_with_style(style):
def add_wrapping(item):
@wraps(item)
def wrapped_item():
return 'a {} wrapped up box of {}'.format(style,str(item()))
return wrapped_item
return add_wrapping
Notice that we've added a new parent function called add_wrapping_with_style, containing everything else from before, and with the parameter of style. Now, we can reference the style parameter within the wrapped_item function. Finally, from our new parent class, add_wrapping_with_style, we can return add_wrapping. Now, we can do something like:
@add_wrapping_with_style('beautifully')
def new_gpu():
return 'a new Tesla P100 GPU!'
print(new_gpu())
Output:
a beautifully wrapped up box of a new Tesla P100 GPU!
Now chaining can start to have some value:
@add_wrapping_with_style('horribly')
@add_wrapping_with_style('beautifully')
def new_gpu():
return 'a new Tesla P100 GPU!'
print(new_gpu())
a horribly wrapped up box of a beautifully wrapped up box of a new Tesla P100 GPU!