Working with Decorators

Mon 15 December 2014

Filed under Python

Decorators: a function within a function

Now a post about decorators. Decorators are quite useful - when you learn how to use them. Here's my attempt at making them easy easier to understand.

First, when might decorators be used?

Decorators are used to extend a function. To give it more functionality. You might want to time how long a function takes. Or, you might want to print a message to the user. Or, you might want to log some data about the function.

Sometimes, you need to use a function that cannot be changed for whatever reason. Maybe you are getting from a library or another module. You can use a decorator to extend the function. Maybe you want to change the formatting. Or something else.

Or, you don't want to change the original function, but you do want to extend it temporarily. You might do this when debugging.

Also, decorators are used in Flask.

So, in very short, what are decorators?

Say, you have a function that returns a date. But you want the date in a different format. You can call that date function and then call another function to format the date that you just got. Or, you can extend the original date function - by wrapping it with a decorator function. In very, very short, the decorator builds on the original function.

What's happening under the hood of decorators?

The decorator function (the outer function) is called. It seeks the original function (the one it is wrapping, the inner function) and the original function is run. Then, the decorator (outer) function is run on the return of the original (inner) function.

It is just like saying: original_func = outer_func(original_func). In other words, the outer function is being run on the return of the original / inner function. Oh, btw, functions can call other functions.

That might not have explained it all, but after this whole post and you see examples, it might make more sense. I'll attempt to break it down - dot by dot, so all dots are connected. So hopefully all will be clear.


OK, with that intro...

So how are decorators designed?

  1. create the function that will be the 'decorator'

  2. create the functions that the decorator function will decorate

  3. wrap the decorated function with the decorator

  4. call the original function. (It is already wrapped and thus the decorator function will also run!)

OK, so that makes it seem so simple. And it is - once you understand how to write the decorator function.

Some pseudo code of the decorator function.

The decorator function (outer function)

1.define the decorator function def decorator(func):.

  • include the decorated function as the argument in the parentheses.

The wrapper function (inner function)

2.define the wrapper function. This function is created on the fly; it wraps around the original function.

  • This can also have args that will be used in the wrapper function. These are pulled from the arguments of the 'decorated function'. For another discussion.

3.(optional) some code - of the wrapper function

  • this can be any code to be done BEFORE the original function is run. Maybe, print a message, collect a start time. Or, you may not have any code here at all.

4.include the original function, the func (the argument in the decorator function) in the code of the wrapper function

  • it can be called on its own. ex: func()
  • or called as part of a statement. ex: return '<em>{}</em>'.format(func())

5.(optional) some code - of the wrapper function

  • this can be any code to be done AFTER the original function is run. Maybe, print a message, collect an end time. Or, you may not have any code here at all.

6.return something in the wrapper function

back to the decorator function (outer function)

7.return what the wrapper function returned

So here is some simple code:

This decorator will format any date in the YYYY_MM_DD format. (It uses .format and strftime directives. See more about strftime directives and .format)

def pretty_date(func):
    def wrapper():
        print 'getting the date from function: {}'.format(func.__name__)
        date = func()
        print 'got the date: {} (before it was formatted)'.format(date)
        return '{0:%Y}_{0:%m}_{0:%d}'.format(date)
    return wrapper

1.def pretty_date(func): : we define the function and include the func as the argument.

  • func is a variable that will call the original function that it is wrapping. Do not use the actual function name.

2.def wrapper(): : this is a function within a function, thus indented.

3.print 'getting the date from function: {}'.format(func.__name__) : We don't really need to put code before the function. This is just to show.

  • func.__name__ gives the name of func

4.date = func() : calls the function it is wrapping and assigns it to date

5.print 'got the date: {} (before it was formatted)'.format(date). We don't really need to put code after the function. This is just to show.

6.return '{0:%Y}_{0:%m}_{0:%d}'.format(date) : Here's the return for the inner/ wrapper function. ---Really, we could have put this line in for line 4. But then, we couldn't have the code to do AFTER the function. (You can't have anything after the return.)

7.return wrapper : returns what the wrapper function returned.

Whew! So, the decorator function has been created. That's the hard part. Now just have to create the functions that will be decorated (or wrapped) with the decorator.

In this case, we will create 3 separate functions - get_today(), get_tomorrrow(), get_yesterday() to show how the same decorator can be used for several functions. (For more about working with dates and datetime and datedelta, check out pymotw.)

import datetime

def get_today():
    return datetime.datetime.today()

def get_tomorrow():
    return datetime.datetime.today() + datetime.timedelta(days=1)

def get_yesterday():
    return datetime.datetime.today() - datetime.timedelta(days=1)

The results are:

2014-12-15 15:47:28.199614
2014-12-16 15:47:28.944899
2014-12-14 15:47:28.944958

Remember, the pretty_date decorator will 'prettify' the date in the format we specified. The pretty_date decorator function will take the return get_today() function and run the wrapper function on it. And return the 'prettified' date.

Here goes the decorated functions. Instead of the above functions standing on their own, we wrap them in the pretty_date decorator. Notice the @ before the decorator pretty_date. Like so:

import datetime

@pretty_date
def get_today():
    return datetime.datetime.today()

@pretty_date
def get_tomorrow():
    return datetime.datetime.today() + datetime.timedelta(days=1)

@pretty_date
def get_yesterday():
    return datetime.datetime.today() - datetime.timedelta(days=1)

You can then call the wrapped functions:

print get_today()
print get_tomorrow()
print get_yesterday()

And now the results are:

2014_12_15
2014_12_16
2014_12_14

And that's decorators. In short.

Oh, and a caveat: Those functions that are now wrapped - are always wrapped. That means that get_today() can't be used to call for the full date 2014-12-15 15:47:28.199614. Whenever, we call get_today(), it will return 2014_12_15 format. You will have to create another function to get the unformatted date.


But that's not all. You can have several decorators wrap a function.

Here, we'll define a few more decorators. These create html tags around the string.

def html_em(func):
    def wrapper():
        return '<em>{}</em>'.format(func())
    return wrapper

def html_strong(func):
    def wrapper():
        return '<strong>{}</strong>'.format(func())
    return wrapper

And here's how we wrap the function with these wrappers too.

@html_em
@html_strong
@pretty_date
def get_today():
    return datetime.datetime.today()

The order matters! It goes from top to bottom. From the top of the ladder down (- and then back up again).

1.First, it will do @html_em decorator function.

  • The func in format(func()) of that function will call the next decorator function @html_strong.
  • And wait for the return from that call to do its job of adding the <em></em>.

2.Then the @html_strong decorator function will begin.

  • The func of the @html_strong's format(func()) will call the @pretty_date decorator function.
  • And wait for the return to do its job of adding the <strong></strong>.

3.Then, the @pretty_date decorator function will call the get_today() function.

  • And wait for the return to do its job of formatting the date.

4.The get_today() function will get today's date.

5.Then it goes back up the ladder. @pretty_date does its work. @html_strong add and@html_emadds`.

And finally, the final return is done. The newly formatted, 'html'ed date!

So in essence, the first thing that is being returned is the datetime.datetime.today(), then it is returning the <strong></strong> and last it is returning the <em></em>

The results:

<em><strong>2014_12_15</strong></em>

for much more on decorators

from stackoverflow - check out the answer 'Decorator Basics'


Comments


DeeKras.com © Dee Kras Powered by Pelican and Twitter Bootstrap. Icons by Font Awesome and Font Awesome More