More Flexible Decorators
Here are resources for more information about decorators and PEP 614:
00:10 A decorator is a callable that wraps a function or a class. They’re typically used to do something before and/or after a function operates. For example, you could create a decorator that logged when the function was entered, called the function, and then logged when the function left.
00:29 A common pattern in web frameworks is to use decorators to ensure that the user is logged in before the view that is the function that displays the web page is allowed to be called. Prior to Python 3.9, decorators had to be a named callable—only the name of a function or a class.
00:48 This means you couldn’t use the entry in a list or the entry in a dictionary as the decorator. You would have to dereference it first, and then decorate with the dereferenced value. Python 3.9 has loosened up the syntax here, and now anything that resolves into a callable expression can be used as a decorator.
01:08 Quite frankly, this is a bit of an edge case. You only come across it if you’re using certain kinds of toolkits, but for those people who are using those toolkits, this does actually simplify the code and mean there’s less code to write.
Let me show you some examples. In case you haven’t seen decorators before, here’s a quick introduction. Don’t forget that all the code being shown here is available in the supplementary materials pull down. This file,
volume.py, declares two decorators.
The first one, called
normal, does nothing. It just returns the function that it’s wrapping. Decorators take as a parameter the function being wrapped, and at some point inside of them have to return that function.
The second decorator,
shout, starting on line 7, is a little more complicated. The nested function inside of the decorator is where the work is done. Similar to the
@normal decorator, it’s just calling the passed-in function, but on line 10, it’s calling
.upper() on whatever the function returns.
This decorator will only work on a function that returns a string—otherwise, it’ll blow up. But now if you’ve got functions with strings, you can wrap it with
@shout to change the returned result.
If I run
first(), I get back the string—the decorator did nothing. If I run
second(), the string is returned,
.upper() is called on it inside of the decorator, and the end result is an all caps shouting. Dickens should cover his ears.
So far, so good. Now, here’s the change in Python 3.9. The decorator on line 11 is only legal in Python 3.9. The difference here is it’s using a dictionary to result in the callable that is wrapping the function called
third(). Prior to Python 3.9, you would have to resolve this in a separate variable, and then use that variable to wrap the function to get it to work. When this module gets loaded, the global value of
voice will be set.
One thing to notice here is that the decorator is bound to the function at the time of module load, so although this module has a global value called
voice, changing it afterwards has no effect.
but it has no effect on the function
third(). Decorators are bound when the module is loaded, so the value of
voice at the time of module load is how the function gets wrapped. Once it’s been wrapped, it stays wrapped.
04:58 As I mentioned before, this is all a little bit of an edge case. You might be thinking to yourself, “So what?” A common pattern in GUI and web programming is to use decorators to indicate the behavior of certain functions. For example, if I was using the GUI toolkit Qt, I can declare a button in the GUI and then use a decorator to associate that button with a function that gets called when the button is clicked.
05:26 This is called a slots pattern. Oftentimes with this kind of coding pattern, you end up with a whole bunch of globals or lists of globals or dictionaries full of globals that you then want to assign the slots for.
Become a Member to join the conversation.