Decorators in Python Tutorial

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:

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:

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:

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:

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: