Decorators

Warning: This section is hard.

Function as Parameter

Recall that, in Python, everything is an object. Even functions. This means that we can actually pass functions as parameters! Check this out.

def is_even(value):
    '''
    Checks if a number is even.
    
    Arguments:
    value -- the number to be checked
    
    Returns:
    Whether value is even
    '''
    
    return value % 2 == 0


def count_instances(some_list, some_function):
    '''
    Counts the number of elements in a list that pass a test.
    
    Arguments:
    some_list -- the list with elements
    some_function -- the test to apply to each element
    
    Returns:
    The number of elements that pass a test
    '''
    
    return sum(elem for elem in some_list if some_function(elem))

Nice! We can actually pass functions as paremeters. You can see how useful this is. Rather than constructing giant if-elif ladders, you can create one function that takes another as a paremeter and simply acts upon that.

So how do we use this?

print count_instances([1, 2, 3, 4, 5], is_even)

Notice that, unlike previously when we call functions, is_even does not have the usual parentheses. This is because we are not calling the function; rather, we are referring to the function object.

Function as Return Value

Since functions, like everything in Python, are objects, they can also be used as return values. Here is where nested functions come in. Here is a trivial example, and then we’ll examine a more useful example.

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

print1 = outer(1)
print2 = outer(2)
print1()
>>> 1
print2()
>>> 2

Interesting. We can return a function, and call it later. In fact, notice that the funciton returned by outer in each case was a different function. This is the critical concept here. The local scope of the formal parameter x is preserved in each call by the new output. Once again, this is called closure.

Now imagine I want to create a function that surrounds words with a pattern. I’ll give this function a long string and it will go through and replace the words I chose with a new pattern.

A concrete example to help you understand.

In markdown, and many other formats, italicizing a string is done by surrounding the string with * on either side. So here is my sample input output.

regular = 'Wow, decorators are far more useful than I initially predicted.'
italicized = transform(regular, ['far', 'I'], surround_width('*'))
print italicized
>>> ' Wow, decorators are *far* more useful than *I* initially predicted.'

Let’s write the transform function.

def transform(string, chosen_words, function):
    result = ''
    for word in string.split():
        if word in chosen_words:
            result += ' {}'.format(function(word))'
        else:
            result += ' {}'.format(word)
    return result

Notice that the third parameter of transform is a function! This way, we can write our code flexibly and transform with many different functions rather than writing many different transform functions.

Now comes the hard part. How do we write this special function? Remember our previous example of closure.

def surround_with(surrounding):
    def surround_with_value(word):
        return '{}{}{}'.format(surrounding, word, surrounding)
    return surround_with_value

Woah. This is a slightly more complicated case of closure that’s happening. First, if you’re not familiar with the string format method, it’s quite simple. Inside any pair of { }, the method inserts the corresponding parameter. For example, '{}{}{}'.format('dude', '!', 'dude') would output dude!dude in a string.

That’s simple enough. It’s just surrounding the word with whatever surrounding is. So now, we return a function that takes one parameter, a word, and surrounds it with surrounding in a string.

In our main transform function, we use surround_with('*'). This means that the function parameter is a function, in this case, that surrounds a given string with '*' on either side.

This notion of closure is very important.

*args and **kwargs

Before we discuss decorators, we need to go over the two special parameters Python has, *args and **kwargs.

The first thing to understand is that args and kwargs can change names; the important thing is the number of asterisks preceding the variable name; usually, however, they are kept as args and kwargs for the sake of convention.

Here is an abbreviated explanation from this excellent post from Stack Overflow.

You would use *args when you’re not sure of how many arguments are in your function. It allows you to pass any number with no problem.

def print_many_things(*args):
    for count, thing in enumerate(args):
        print '{0}. {1}'.format(count, thing)

print_many_things('apple', 'banana', 'cabbage') 

Would produce the output

0. apple
1. banana
2. cabbage

Nice. What about **kwargs? This is used for named variables.

def table_things(**kwargs):
    for name, value in kwargs.items():
        print '{0} = {1}'.format(name, value)

table_things(apple = 'fruit', cabbage = 'vegetable')

Would produce the output

cabbage = vegetable
apple = fruit

Decorators

Okay, now we have established most of the background needed for decorators. (Phew! I told you it wasn’t easy…)

A decorator function is one that takes a function (the function to be decorated) and returns a new function (the original function with the decoration applied). Think of a decoration as a change.

Imagine we’re in charge of a shopping complex, like Walmart. And you’re writing their backend to manage accounts. So you have some class Product, filled with useful materials.

As you can imagine, there are going to be many instances when you append the classic “$” in front of the result. So let’s write the decorator function to do that.

def currency(f):
    def wrapper(*args, **kwargs):
        return '$' + str(f(*args, **kwargs))
    
    return wrapper

Why do we give the function f the arguments *args and **kwargs? To be more flexible. Since we want this to work for as many situations as possible, and we don’t know the exact arguments that f takes, we need to do that.

Now imagine we have some long complex function that adjusts all prices to tax.

class Product(database):

    def price_with_tax(self, tax_rate):
        return price * (1 + (tax_rate * 0.01))

And there are many others like it. How can we apply our decorator to it? Python has the shortcut @ reserved for decorators. This minor change is all we have to do!

class Product(database):

    @currency
    def price_with_tax(self, tax_rate):
        return price * (1 + (tax_rate * 0.01))

Wow! Yup, that’s it. Python will automatically wrap the price_with_tax function around the decorator we wrote!

The power of this decorator style format is unbelievable. In fact, whole web development systems like Flask use and abuse it. Now that you know the basics, implement more of your own decorators!

References

Another great resource for learning Python is Jeff Knupp’s blog, where many of these examples are from. The material is excellent and quite readable. Even beginners can understand what’s going on.