Python Decorators by Example

Written by Dan Sackett on October 1, 2014

So you want to wrap a function in a function and wrap that function in another function. It's all good, just use decorators.

The decorator design pattern is a well-known example of how you can extend a class or function with another class or function. It provides a simple solution to adding functionality without disturbing the logic in your well-tested functions. I think the easiest example to understand of this principle is Django's login_required decorator. We will create a regular view in Django and then we can add @login_required above the definition and all of a sudden we have a view that will redirect you if you are not logged in.

What's happening?

Well the login_required decorator is simply a function. When you declare it as a decorator, you are saying "do this function and then do my function". In reality, the login_required function is actually calling your function from inside of it. This might still be confusing, but I think the best way to dive into this is by example.

A Function Takes a Function

In Python, functions are simply objects. In fact, everything is an object in Python. With that in mind, it makes sense that you're allowed to pass a function an argument for another function. This might look something like this:

def my_decorator(a_function_to_decorate):
    # We defined a function within a function to wrap our passed in function
    def wrapper_function():
        print "Before the function runs"

        a_function_to_decorate()

        print "After the function runs"

    # We return the wrapper which will execute the before and after code
    # around our function
    return wrapper_function

# So let's have an immutable function
def dont_touch_me():
    print "If you modify me, the world may explode"

dont_touch_me() 
# outputs: If you modify me, the world may explode

# We can extend this with the decorator we made
decorated_function = my_decorator(dont_touch_me)
decorated_function()
# outputs:
# Before the function runs
# If you modify me, the world may explode
# After the function runs

So learning by example, we can see that our decorator actually defines another function inside of itself. This is completely legal and we need to do it in the case of a decorator. It allows us to wrap some kind of before and after processing around the function parameter. We call our passed in function within this inner function and return the inner function. This way, when we declare our decorator it will execute the inner function which ultimately also executes the passed in function.

Hopefully the example speaks better than I do on this.

Passing in functions feel a little weird? Python has our back with the handy decorator syntax:

@my_decorator
def dont_touch_me_either():
    print "Leave me alone"

dont_touch_me_either()  
# outputs:  
# Before the function runs
# Leave me alone
# After the function runs

This is familiar to the initial example I gave about @login_required. In python, it knows that the function that follows will be an argument for the decorator. The advantage to this? You don't need to define a new variable to store the result of the decorated function.

So what happens when I want to decorate my decorated function? You're not crazy to want to do this and it's actually super easy to accomplish.

def date(func):
    def wrapper():
        print "October 1, 2014"
        func()
    return wrapper

def to(func):
    def wrapper():
        print "To Dan Sackett,"
        func()
    return wrapper

def signature(func):
    def wrapper():
        func()
        print "Sincerely, The State of Florida"
    return wrapper

@date
@to
@signature
def document():
    print "I hereby declare you the coolest dude in Florida!"

document()
# outputs:
# October 1, 2014
# To Dan Sackett,
# I hereby declare you the coolest dude in Florida!
# Sincerely, The State of Florida

So in this example, we build a simple document using decorators. We can easily add or remove portions of this letter by changing or removing a decorator from the stack.

Why do it this way?

Imagine you have a congratulations document, a notice document, and a warning document. All three of these documents will have different body copy but will likely want the same format. While we can create an abstract class to handle something like this, decorators are another solution that will keep our code DRY and give us the flexibility to creating any number of components for a document.

Take note though: order matters. If you were to change the order of these decorators, you would not get what you expected. Decorators operate linearly and will move down the stack until it reaches the last function definition.

Passing arguments to a decorator

What happens when your function takes arguments and you want them available in the decorator. This is supported and very common.

def my_new_decorator(function_to_decorate):
    def wrapper(arg1, arg2):
        function_to_decorate(arg1, arg2)
    return wrapper

@my_new_decorator
def desert_island(item1, item2):
    print "If I was stranded on a desert island, I would take with me", item1, "and", item2

desert_island("My computer", "Solar panel battery")
# outputs:
# If I was stranded on a desert island, I would take with me My computer and Solar panel battery

Pretty cool, but how does this work?

When you call the function that the decorator returns, you call our wrapper function. If you pass your arguments to this wrapper function then we can use them in the decorated function.

Wrapping Methods

You may have seen the @property or @classmethod decorators in Python before when working with a class. Creating decorators like this for a class works exactly like we did before.

def method_decorator(method_to_decorate):
    def wrapper(self, reported_speed):
        reported_speed -= 5 # Subtract 5 from the speed we think we're going
        return method_to_decorate(self, reported_speed)
    return wrapper


class Officer(object):

    def __init__(self):
        self.speed_limit = 45
        self.clocked_speed = 55

    @method_decorator
    def pull_over(self, reported_speed):
        print "Officer: You were going %s mph in a %s mph zone" % (self.clocked_speed, self.speed_limit)
        print "Me: I'm sorry I thought I was only going %s mph" % (reported_speed)

o = Officer()
o.pull_over(40)
# outputs
# Officer: You were going 55 mph in a 45 mph zone
# Me: I'm sorry I thought I was only going 35 mph

You can see that wrapping a method is exactly the same as wrapping any other function. Again, this really allows our code to be DRY and gives us a good way to write less complicated code. Notice that we pass self to the wrapper in the decorator definition since the method expects self. Sometimes that can be easy to forget.

Passing arguments to a Decorator

In some cases, we may want to pass arguments to the decorator itself. For instance, we might want to only show a view if a user has "edit" permissions. By creating a decorator that takes functions, we can reuse the decorator passing in permissions to make it very robust.

def permission_required(permission):

    print "We are checking for", permission, "rights"

    # We pass the function to the decorator
    def my_decorator(func):
        print "Inside the decorator with permission:", permission

        # Remember that we pass function args to wrapped
        def wrapped(user) :
            if permission in user.get("permissions"):
                return func(user)
            else:
                print "You do not have permission"

        return wrapped

    return my_decorator

@permission_required("edit")
def my_view(user):
    print "Inside decorated function with name:", user.get("name")

me = {
    "name": "Dan Sackett",
    "permissions": ["edit", "view"]
}

invalid = {
    "name": "Invalid Creds",
    "permissions": ["view"]
}

my_view(me)
print "\n"
my_view(invalid)
# outputs:
# We are checking for edit rights
# Inside the decorator with permission: edit
# Inside decorated function with name: Dan Sackett

# You do not have permission

Some things to note here:

While this looks a bit more complicated, in reality it is simply a function inside a function inside a function. When you break it down into pieces, it can be a lot easier to visualize what's actually happening.

Using Functools

Python ships with a fun little package called functools. In it, we have a magical method called wraps() which is in fact a decorator itself. If you read source code a lot, you may have seen this used when working with decorators. It copies the name, module, and docstring of the decorated function to its wrapper. It is considered best practice to use it so check it out:

import functools

def bar(func):
    # We say that "wrapper", is wrapping "func"
    # and the magic begins
    @functools.wraps(func)
    def wrapper():
        print "bar"
        return func()
    return wrapper

@bar
def foo():
    print "foo"

print foo.__name__

You'll notice that we pass the wrapped function to the wraps() decorator and it tells the system that when "foo is the function we're working with. While it has a decorator, know that foo is our guy."

Conclusion

When I first started Python, decorators were hard to me. I never looked into the internals and the reasons for why they worked. Now that I can break them down, they are much easier to grasp. If you need more information, most of this post was inspired by this great Stack Overflow question.

I hope you learned a few new things today about decorators or at least how to properly construct them.


decorators python

comments powered by Disqus