DRY Your Python Code With Decorators

Decorators are one of the nicest features of Python, yet for the beginner Python programmer, they can seem like magic. The purpose of this article is to understand, in depth, the mechanism behind Python decorators.

Here's what you'll learn:

  • what are Python decorators and what they are good for
  • how to define our own decorators
  • examples of real-world decorators and how they work
  • how to write better code using decorators

Introduction

In case you haven't seen one yet (or perhaps you didn't know you were dealing with one), decorators look like this:

You usually encounter them above the definition of a function, and they're prefixed by @. Decorators are especially good for keeping your code DRY (Don't Repeat Yourself), and they do that while also improving the readability of your code.

Still fuzzy? Don't be, since decorators are just Python functions. That's right! You already know how to create one. In fact, the fundamental principle behind decorators is function composition. Let's take an example:

What if we wanted to create another function, x_plus_2_squared? Trying to compose the functions would be futile:

You cannot compose functions in this way because both functions take numbers as arguments. However, this will work:

Let's redefine how x_squared works. If we want x_squared to be composable by default, it should:

  1. Accept a function as an argument
  2. Return another function

We'll name the composable version of x_squared simply squared.

Now that we've defined the squared function in a way that makes it composable, we can use it with any other function. Here are some examples:

We can say that squared decorates the functions x_plus_2x_plus_3, and x_times_2. We are very close to achieving the standard decorator notation. Check this out:

That's it! x_plus_2 is a proper Python decorated function. Here's where the @ notation comes into place:

In fact, the @ notation is a form of syntactic sugar. Let's try that out:

If squared is the first decorator you've ever written, give yourself a big pat on the back. You've grasped one of the most complex concepts in Python. Along the way, you learned another fundamental feature of functional programming languages: function composition.

Build Your Own Decorator

A decorator is a function that takes a function as an argument and returns another function. That being said, the generic template for defining a decorator is:

In case you didn't know, you can define functions inside functions. In most cases, the decorated_function will be defined inside decorator.

Let's look at a more practical example:

Sweet! Now you can be sure that everything inside your app is standardised for the UTC timezone.

A Practical Example

Another really popular and classic use-case for decorators is caching the result of a function:

If you look at the code shallowly, you might object. The decorator isn't reusable! If we decorate another function (say another_complex_computation) and call it with the same parameters then we'll get the cached results from the complex_computation function. This won't happen. The decorator is reusable, and here's why:

The cached function is called once for every function it decorates, so a different _cache variable is instantiated every time and lives in that context. Let's test this out:

Decorators in the Wild

The decorator we just coded, as you may have noticed, is very useful. It's so useful that a more complex and robust version already exists in the standard functools module. It is named lru_cache. LRU is the abbreviation of Least Recently Used, a caching strategy. 

One of my favourite uses of decorators is in the Flask web framework. It is so neat that this code snippet is the first thing you see on the Flask website. Here's the snippet:

The app.route decorator assigns the function hello as the request handler for the route "/". The simplicity is amazing. 

Another neat use of decorators is inside Django. Usually, web applications have two types of pages: 

  1. pages you can view without being authenticated (front page, landing page, blog post, login, register)
  2. pages you need to be authenticated to view (profile settings, inbox, dashboard)

If you try to view a page of the latter type, you'll usually get redirected to a login page. Here's how to implement that in Django:

Observe how neatly the private views are marked with login_required. While going through the code, it is very clear to the reader which pages require the user to log in and which pages do not.

Conclusions

I hope you had fun learning about decorators because they represent a very neat Python feature. Here are some things to remember:

  • Correctly using and designing decorators can make your code better, cleaner, and more beautiful.
  • Using decorators can help you DRY up your code—move identical code from inside functions to decorators.
  • As you use decorators more, you'll find better, more complex ways to use them.

Remember to check out what we have available for sale and for study on Envato Market, and don't hesitate to ask any questions and provide your valuable feedback using the feed below.

Well, that's that about decorators. Happy decorating!

Tags:

Comments

Related Articles