Get startedGet started for free

Timeout(): a real world example

1. Timeout(): a real world example

We're going to finish up by looking at an example of a real-world decorator that takes an argument to give you a better sense of how they work.

2. Timeout

For our first example, let's imagine that we have some functions that occasionally either run for longer than we want them to or just hang and never return.

3. Timeout

It would be nice if we could add some kind of timeout() decorator to those functions that will raise an error if the function runs for longer than expected.

4. Timeout - background info

To create the timeout() decorator, we are going to use some functions from Python's signal module. These functions have nothing to do with decorators, but understanding them will help you understand the timeout() decorator I am about to show you. The raise_timeout() function simply raises a TimeoutError when it is called. The signal() function tells Python, "When you see the signal whose number is signalnum, call the handler function." In this case, we tell Python to call raise_timeout() whenever it sees the alarm signal. The alarm() function lets us set an alarm for some number of seconds in the future. Passing 0 to the alarm() function cancels the alarm.

5. Timeout in 5 seconds

We'll start by creating a decorator that times out in exactly 5 seconds, and then build from there to create a decorator that takes the timeout length as an argument. Our timeout_in_5s() decorator starts off by defining a wrapper() function to return as the new decorated function. Returning this function is what makes timeout_in_5s() a decorator. First wrapper() sets an alarm for 5 seconds in the future. Then it calls the function being decorated. It wraps that call in a try block so that in a finally block we can cancel the alarm. This ensures that the alarm either rings or gets canceled. Remember, when the alarm rings, Python calls the raise_timeout() function. Let's use timeout_in_5s() to decorate a function that will definitely timeout. foo() sleeps for 10 seconds and then prints "foo!". If we call foo(), the 5-second alarm will ring before it finishes sleeping, and Python will raise a TimeoutErrror.

6. Timeout in n seconds

Now let's create a more useful version of the timeout() decorator. This decorator takes an argument. To decorate foo() we'll set the timeout to 5 seconds like we did previously. But when decorating bar(), we can set the timeout to 20 seconds. This allows us to set a timeout that is appropriate for each function. timeout() is a function that returns a decorator. I like to think of it as a decorator factory. When you call timeout(), it cranks out a brand new decorator that times out in 5 seconds, or 20 seconds, or whatever value we pass as n_seconds. The first thing we need to do is define this new decorator that it will return. That decorator begins, like all of our decorators, by defining a wrapper() function to return. Now because n_seconds is available to the wrapper() function we can set an alarm for n_seconds in the future. The rest of the wrapper() function looks exactly like the wrapper() function from the timeout_in_5s() function. Notice that wrapper() returns the result of calling func(), decorator() returns wrapper, and timeout() returns decorator. So when we call foo(), which has a 5-second timeout, it will timeout like before. But bar(), which has a 20-second timeout, prints its message in 10 seconds, so the alarm gets canceled.

7. Let's practice!

The following exercises will give you a few chances to try your hand at creating decorators that take arguments.