Click here to Skip to main content
Click here to Skip to main content

Tagged as

Decorators in Python

, 14 Feb 2014
Rate this:
Please Sign up or sign in to vote.
Today I’ll turn you into a decorator. Let’s build a house and decorated it.First things first… the house. In my world it’s very simple to build a house, just call the function build_house().After the “construction”, in order to live there, we need to turn it cozier. For this effect we can call a dec

Today I’ll turn you into a decorator. Let’s build a house and decorated it. First things first… the house. In my world it’s very simple to build a house, just call the function build_house(). After the "construction", in order to live there, we need to turn it cozier. For this effect we can call a decorator to shine things up. When she arrives and sees the house she says: I’m going to decorate it with paintings on the wall. With no further delay she: @decorate("paintings").

def decorate(thing):
    def _decorate(func_name):
        def __decorate(*args, **kwargs):
            ret = func_name(*args, **kwargs)
            if thing is not None:
                ret += " decorated with %s on the wall" % thing
            return ret
        return __decorate
    return _decorate 

@decorate("paintings")
def build_house():
    return "This is a house"
>>> print build_house()
This is a house decorated with paintings on the wall

##Basic Usage Decorators are used to change a function behaviour not by altering its code but by trapping the entry and exit points. Suppose you want to log the execution time of a function or make it return different values according to additional rules. The code to do that fits perfectly in a decorator. ## Explanation When a function is decorated, it is run inside the decorator. This way, it is granted to the decorator the power to do the things described earlier. ###Functions return functions Functions are first class objects. That means a function is indeed an object. Objects can be passed as arguments and be returned by functions. Therefore, functions can consume functions and return functions (replace the word functions with objects and you get a true statement as well).

The statement functions can return functions implies that an argument of a function can be a function and/or a function can be defined inside a function.

I’m getting sick of writing the word function but let’s continue with a piece of code.

def outer():
    def inner():
        print 'inner'
    return inner

fn = outer()
fn()  # prints inner

The code above defines the function inner inside outer. When we run outer(), the fn variable becomes equal to the function inner.

The line 4 returns the inner function without calling it (did you notice the lack of parentheses?).

Now if we call fn by adding parentheses () we get the execution of inner.

This leads us to…

Closures

From Wikipedia… >A closure—unlike a plain function pointer—allows a function to access those non-local variables even when invoked outside of its immediate lexical scope.

This means that our inner function can "remember" non-local variables, in our case, variables defined in outer function.

def outer():
    x = 1
    def inner():
        print 'Got value %i from non-local' % x
    return inner
    
fn = outer()
del outer  # deletes object outer
fn()  # 'Got value 1 from non-local'

Even deleting the function outer, fn() still prints x variable.

Functions as arguments

def run_me():
    print "I'm running baby"

def outer(func):
    def inner():
        print "Before running the function"
        func()
    return inner
    
r = outer(run_me)
del run_me  # deletes run_me function
r()  # "I'm running baby"

On line 9, r gets function inner, returned by outer, which calls run_me (closure), which prints "I’m running baby".

Decorate me!

Now let’s try some decorator magic.

def outer(func):
    def inner():
        print "Before running the function"
        func()
    return inner

@outer
def run_me():
    print "I'm running baby"

run_me()  #call run_me and prints the next 2 lines
Before running the function
I'm running baby

This is a simple example of a decorator. To apply the decorator to a function you use @ symbol followed by the decorator name. Here you no longer need to use ‘r = outer(run_me)’ and ‘r()’. Much simpler!

How about if we change the definition of function run_me and add a parameter name to it? Will the decorator still works? Let’s try!

def outer(func):
    def inner():
        print "Before running the function"
        func()
    return inner

@outer
def run_me(name):
    print "I'm running %s" % name

run_me("Hugo")  # error below
TypeError: inner() takes no arguments (1 given)

We get an error saying function inner takes no arguments but we tried to pass the name "Hugo". Sadly it doesn’t work. Happily we know how to fix it Smile | :)

To fix it we add a parameter ‘name’ to the function inner and then add the argument to the function call on line 4 below.

def outer(func):
    def inner(name):
        print "Before running the function"
        func(name)
    return inner

@outer
def run_me(name):
    print "I'm running %s" % name

run_me("Hugo")  # now it works
#Before running the function
#I'm running hugo

Ok, so far so good. But what happens if I change the definition of the function run_me and add some more parameters? It will FAIL! Let’s make the decorator more generic so we don’t have to worry too much.

*args and **kwargs

*args and **kwargs are conventional ways to get the arguments passed to a function.

For more info on *args and **kwargs please refer to one of the many posted questions on Stackoverflow or Python docs.

As we want to abstract from the number of arguments passed to the function inner we replace ‘def inner(name):’ with ‘def inner(*args, **kwargs):’ and ‘func(name)’ with ‘func(*args, **kwargs)’.

def outer(func):
    def inner(*args, **kwargs):
        print "Before running the function"
        func(*args, **kwargs)
    return inner

@outer
def run_me(name):
    print "I'm running %s" % name

run_me("Hugo")  # now it works
#Before running the function
#I'm running hugo

Final touch - passing arguments to the decorator

This step is only required if you want to add some salt to the decorator, changing the way it works when certain arguments are passed to it. A lot can be "decorated" without this step.

The decorator function now has a parameter that indicates if we run it in debug mode or not. To get this, we add another "level" of functions. As you can see from the code below the decorator is composed by the function outer (with takes the debug argument), the function _outer (which takes the ‘function’ argument) and the function inner (which takes the arguments to the function to be decorated).

def outer(debug=False):
    def _outer(func):
        def inner(*args, **kwargs):
            if debug:
                print "Before running the function"
            func(*args, **kwargs)
        return inner
    return _outer

@outer(False)  # decorated is NOT in debug mode
def run_me(name):
    print "I'm running %s" % name

run_me("Hugo")
# I'm running Hugo

This concludes the post.

Disclaimer

Much of the ideas described here came from Simeon Franklin’s post on decorators. This post is available at .

Decorators in Python was originally published by Hugo Santos at Never Pop on January 08, 2013.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

hugocaracol

Portugal Portugal
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.140827.1 | Last Updated 14 Feb 2014
Article Copyright 2014 by hugocaracol
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid