More Advanced Decorators

Learn how to pass parameters to decorators using nested functions and decorator objects.

With the introduction we've just had, we now know the basics of decorators: what they are and their syntax and semantics. Now we're interested in more advanced uses of decorators that will help us structure our code more cleanly.

We'll see that we can use decorators to separate concerns into smaller functions, and reuse code, but in order to so do effectively, we'd like to parametrize the decorators (otherwise, we'll end up repeating code). For this, we'll explore different options on how to pass arguments to decorators.

Passing arguments to decorators

At this point, we already regard decorators as a powerful tool in Python. However, they would be even more powerful if we could just pass parameters to them so that their logic is abstracted even more.

There are several ways of implementing decorators that can take arguments, but we'll go over the most common ones. The first one is to create decorators as nested functions with a new level of indirection, making everything in the decorator fall one level deeper. The second approach is to use a class for the decorator (that is, to implement a callable object that still acts as a decorator).

In general, the second approach favours readability more, because it is easier to think in terms of an object than three or more nested functions working with closures. However, for completeness, we will explore both, and you can decide what is best for the problem at hand.

Decorators with nested functions

Roughly speaking, the general idea of a decorator is to create a function that returns another function (in functional programming, functions that take other functions as parameters are called higher-order functions, and it refers to the same concept we're talking about here). The internal function defined in the body of the decorator is going to be the one being called.

Now, if we wish to pass parameters to it, we then need another level of indirection. The first function will take the parameters, and inside that function, we'll define a new one, which will be the decorator, which in turn will define yet another new function, namely the one to be returned as a result of the decoration process. This means that we'll have at least three levels of nested functions.

One of the first examples we saw of decorators implemented the retry functionality over some functions. This is a good idea, except it has a problem; our implementation did not allow us to specify the number of retries, and instead, this was a fixed number inside the decorator.

Now, we want to be able to indicate how many retries each instance is going to have, and perhaps we can even add a default value to this parameter. In order to do this, we need another level of nested functions—first for the parameters, and then for the decorator itself.

This is because we are now going to have something in the form of the following:

Get hands-on with 1200+ tech skills courses.