Good Uses for Decorators

Learn some of the most common and relevant uses of decorators.

Let's take a look at some common patterns that make good use of decorators. There are common situations for when decorators are a good choice.

Although there are countless applications that decorators can be used for, we will just enumerate the most common or relevant ones:

  • Transforming parameters: Changing the signature of a function to expose a nicer API, while encapsulating details on how the parameters are treated and transformed underneath. We must be careful with this use of decorators, because it's only a good trait when it's intentional. That means, if we are explicitly using decorators to provide a good signature for functions that had a rather convoluted one, then it's a great way of achieving cleaner code by means of decorators. If, on the other hand, the signature of a function changed inadvertently because of a decorator, then that's something we would want to avoid.

  • Tracing code: Logging the execution of a function with its parameters. There are multiple libraries that provide tracing capabilities, and they often expose such functionality as decorators to add to our functions. This is a nice abstraction, and a good interface to provide, as a way of integrating the code with external parties without too much disruption. In addition, it's a great source of inspiration, so we can write our own logging or tracing functionality as decorators.

  • Validating parameters: Decorators can be used to validate parameter types (against expected values or their annotations, for example) in a transparent way. With the use of decorators, we can enforce preconditions for our abstractions, following the ideas of designing by contract.

  • Implementing retry operations: We can do this in a similar way to the example we explored in the previous section.

  • Simplifying classes by moving some (repetitive) logic into decorators: This is related to the DRY principle, which we'll revisit toward the end of this lesson.

Let's discuss a few of these topics in more detail.

Adapting function signatures

Imagine a scenario in which you're working with legacy code, and there's a module that contains lots of functions defined with a complex signature (lots of parameters, boilerplate, etc.). It would be nice to have a cleaner interface to interact with these definitions but changing lots of functions implies a major refactor. We can use decorators to keep the differences in the changes to a minimum.

Sometimes we can use decorators as an adapter between our code and a framework we're using, if, for example, that framework has these considerations.

As another example, imagine we have a framework that expects to call functions defined by us, maintaining a certain interface:

Get hands-on with 1200+ tech skills courses.