Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set the default subtitles language in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

More Flexible Decorators

Here are resources for more information about decorators and PEP 614:

00:00 In the previous lesson, I showed you the new pipe and pipe equal operators for dictionaries. In this lesson, I’m going to show you the changes to decorator syntax.

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.

01:21 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.

01:39 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.

01:52 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.

02:11 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 @normal or @shout to change the returned result.

02:27 Here’s an example of them in use. Inside of the before file, I import the normal and shout decorators from volume.

02:34 The function on line 5 is wrapped with @normal. The function on line 9 is wrapped with @shout.

02:45 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.

03:05 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.

03:37 It does that by prompting for input. The input prompt will allow you to choose a string and then use that string to dereference the decorator inside of the DECORATORS dictionary.

03:50 Thus, at load time of the module, it’s decided whether or not @normal or @shout is being applied to the function called third(). Let’s look at this in practice.

04:02 When I import the module, it prompts me to choose something. I’ve chosen shout, and now if I call the function, I get back a very loud response.

04:15 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.

04:31 voice is currently 'shout', because it was set at the module load time.

04:38 I can change it without a problem,

04:43 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.

05:38 In that case, before Python 3.9, you had to do extra work to use these things as decorators. Now with the change in the syntax, it’ll make coding with these kinds of frameworks easier.

05:52 Next up, I’ll show you the new features for annotation inside of Python 3.9.

Become a Member to join the conversation.