Locked learning resources

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

Unlock This Lesson

Locked learning resources

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

Unlock This Lesson

Taking and Returning Functions With Decorators

00:00 Another way of using the return statement for returning functions is to write decorator functions.

00:07 A decorator function takes a function object as an argument and returns a function object. The decorator processes the decorated function in some way and then returns either that function object or replaces it with a function or other type of callable object. Decorators are useful when you need to add extra logic to an existing function but you don’t want to modify the original function itself.

00:35 There exist many decorators in Python. With classes and objects, you’ll see decorators for class methods, static methods, and properties. Web frameworks use decorators to control access, and you’ll see decorators and other libraries as well.

00:52 And you can write your own. For example, you might want to code a function to log its function calls, validate the arguments to a function, or measure the execution time of a function. Again, any situation where you need to add additional logic to a function without directly changing that function is a good place to use a function decorator. As an example, suppose you want to create a function that finds the execution time of another function. Based on what you saw in the last lesson, you could create a function to do the timing, which takes as an argument the function you want to time. Since it’s using timing functions, it needs to import the time module.

01:39 I’ll have more to say about that near the end of the lesson. Next is to define a function whose purpose is to time the execution of another function. That function that you wish to time will be used as an argument to this function.

01:54 In it is an inner function, which will handle all of the processing. We find the system time that the function begins, then call the function provided as an argument to the outer function, allowing you to use any positional or keyword arguments that function was provided and then saving its return value to result.

02:16 Find the time when the function ends execution, then subtract the end and start times to find the duration. The inner function then returns whatever the result was.

02:30 The higher-ordered function returns this inner-function object, which remembers what function was provided as an argument much in the same way multiply() from the previous lesson remembered the 2 and the 3 for double() and triple().

02:46 We then use a decorator—in this case, @my_timer—to indicate that the function my_timer() should decorate the function that follows.

02:56 I’ll explain what that means in a moment. In this case, it’s a function to compute the mean of a set of data. We have a time delay of 1 second, just to make determining the execution time more interesting.

03:10 And then we return the mean. Applying what you learned last lesson, you should be expecting some_new_function = my_timer(delayed_mean), where we pass delayed_mean as the argument to my_timer().

03:25 This would create a new callable object made from the return value, _timer, which remembers that it was made with delayed_mean() as the argument func. But that’s not how this is done in Python.

03:41 Here’s where that @ notation comes in. When you indicate that my_timer() is decorating delayed_mean(), Python creates the function produced by my_timer() passed with delayed_mean as an argument and saves that back to the name delayed_mean.

04:01 So now when you call delayed_mean(), it will use the decorated function, which in this case, will time the execution of delayed_mean().

04:12 So, let’s try this out. I’ll import our functions and call delayed_mean() on some data.

04:22 I’m trying to use the same data we’ve seen in previous examples.

04:30 It took a little bit of time to run, but eventually the inner function produced the execution time of 1.00 something. And that’s going to vary from one function call to the next, depending on what your computer happens to be doing at the same time.

04:45 And then we got the mean of the data, 6.5, which for that set of data should always evaluate to six and a half.

04:54 So, what’s happening? Python runs decorator functions as soon as you import or run a module or script. In this case, when you called delayed_mean(), you were really calling the object returned from my_timer(), which here was the function object _timer.

05:13 The call to the decorated delayed_mean() will return the mean of the original set of data and will also measure the execution time of the original delayed_mean().

05:24 Notice that we haven’t changed any of the logic of delayed_mean().

05:30 A decorator function essentially wraps itself around the function it decorates. If there was another function whose execution time you were interested in, all you would have to do is decorate that one with the @my_timer decorator as well.

05:44 And remember, we did this to create additional logic—in this case, to time the execution of a function—without changing the function we wanted to time. And that is the role of decorators.

05:57 About time(), we use the function time() to help compute the execution time of a function call. This function is in a module called time, which you saw me import, that provides a set of time-related functions.

06:11 This time() function returns the number of seconds that has elapsed since some time designated as time equals zero on your computer. This time zero is referred to as the epoch time and its precise date and time depend on your computer and operating system. Finally, here are some additional Real Python resources you may want to look at for more detailed information on some of the material presented here.

06:39 Next, we’ll look at something called the factory pattern.

Avatar image for cordovez

cordovez on Dec. 13, 2023

The obvious use for decorators would be to add functionality to existing code without breaking anything.

However, libraries such as FastAPI use decorators a lot, so I can start to see the importance of understanding how they work

Become a Member to join the conversation.