Decorators with *args, **kwargs

Tue 16 December 2014

Filed under Python

Decorators: Decorators - with *args and **kwargs

This post builds on my previous one about Decorators.

So we have the decorator that 'prettifies' the date returned from the original function.

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

This @pretty_date decorator can be used with any function that returns a date, no matter how that date is calculated. As long as it is in a format that the return '{0:%Y}_{0:%m}_{0:%d}'.format(date) can evaluate.

But what if the original function needs some arguments in order for it to get the date? Yes, those arguments can be passed through the decorator to the function. It's rather simple. Just use *args and **kwargs. Read more about *args and **kwargs.

Let's say we have this function:

from datetime import datetime, timedelta

def date_into_future(date, days):
    date_format = "%m/%d/%Y"
    a = datetime.strptime(date, date_format)
    return a + timedelta(days=days)

It has 2 arguments: a date and numbers of days into the future. All it does is add the number of days to the original date. And then later we want to 'prettify' it with the @pretty_date decorator.

Or we have this function:

from datetime import datetime, timedelta

@pretty_date
def date_in_past(month, day, days, year=2014):
    date = '{}/{}/{}'.format(month, day, year)
    date_format = "%m/%d/%Y"
    a = datetime.strptime(date, date_format)
    return a - timedelta(days=days)

This function has 4 arguments. The first 3 are positional, the last is a keyword argument. OK, this is just for argument learning sake. This function gets a date and subtracts a number of days from it. And then of course, we want to 'prettify' it.

These and the other functions from the previous post all can be decorated with the same @pretty_date decorator. But since they all have different amounts of args and even kwargs, we have to design the decorator to be able to handle all possible functions that might be wrapped by it.

Here's the revised decorator:

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

Notice the *args and **kwargs in the def wrapper(*args, **kwargs) and again in the date = func(*args, **kwargs). They are included in the wrapper and then passed to the original function. Which of course uses those arguments to get the date.

And that's it! You can use this decorator with many different functions - whether they do or do not have args or kwargs.

Here's how you might call these 2 functions. (Remember, once the functions are wrapped with the decorator, they are always wrapped. So when we call the function, they are automatically decorated.)

print date_into_future('12/8/2014', 30)
print date_in_past(year=2014, month=1, day=10, days=30)
print date_in_past(2, 28, 45, year=2014)

And the results:

getting the date from function: date_into_future
the date: 2015-01-07 00:00:00 (before it was formatted)
2015_01_07


getting the date from function: date_in_past
the date: 2013-12-11 00:00:00 (before it was formatted)
2013_12_11


getting the date from function: date_in_past
the date: 2014-01-14 00:00:00 (before it was formatted)
2014_01_14

Comments


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