Thursday, March 31, 2016

Python Closures and Decorators

What are decorators?

Python has a great feature called decorators. In short, a decorator allows us to inject or modify the code in other functions or classes. There are function decorators and class decorators. This article is focused on function decorators.

Please be aware that the terms "outer function" and "parent function" are used interchangeably in this post, as are "inner function" and "child function".

Functions are first-class objects

In Python, everything is an object, including functions. Functions are first-class objects and, just like any other object (integers, strings, lists), they can be passed to other functions as arguments, assigned to variables, even returned from other functions.

Nested functions

We can also define functions inside functions. Nested functions are useful for encapsulation purposes (isolating inner functions from the global scope).

def parent():
     """Outer function"""
     print("Hello from parent() function.")

     def child():
         """Inner function"""
         return "Hello from child() function."

     print(child())

Inner functions are only visible inside the outer function. If we call the inner function from outside the outer function, we get a NameError.

Closures

In order to understand decorators, we must first understand closures. A closure is an inner function that remembers values set in the the outer function scope.
def outer(msg):
     """Outer function"""

     def inner():
         """Inner function"""
        print(msg)

     inner()
When the above code is executed, the following happens:
outer("Hello")
Hello

Notice that the inner function was able to access the value of the msg variable, which is defined in the outer function.

In the example above, the inner function is called on the last line of the outer function. What happens if, instead of calling the inner function, the outer function returned it?

def outer(msg):
     """Outer function"""

     def inner():
         """Inner function"""
         print(msg)

     return inner()

When the above code is executed, the following happens:
my_function = outer("Hello from the inner function")
my_function
Hello from the inner function

The outer() function is a factory function
Basically, we have a closure when:
  • There are nested functions (a function inside another function).
  • The inner function refers to a value defined in the outer function.
  • The outer function returns the inner function.

Function decorators

A decorator is a function that:
  1. Takes other function as an argument.
  2. Adds to or modifies the function's code.
  3. Returns the modified function.
The preferred syntax for using decorators is:
def my_decorator(func):

    def wrapper():
        print("Run something before func() is called.")
        func()
        print("Run something after func() is called.")

    return wrapper

@my_decorator
def my_function():
    print('Hello from my_function.')
The "@" in the line above a function indicates the application of a decorator. In the above example, my_decorator is applied to my_function(). The output will be:
my_function()
Run something before func() is called
Hello from my_function.
Run something after func() is called.

Chaining decorators

It's possible to apply multiple decorators to a function. 
@some_decorator
@other_decorator
def my_function():
    print('Hello from my_function.')
The bottommost decorator (the one closest to the function) is executed first, then the function returned by this decorator is passed to the decorator above it and so on.

A future post will talk about class decorators.

3 comments: