Closures

Before getting into closures lets understand nested functions. A function defined inside another function is called a nested function. Nested functions can access variables of the enclosing scope.

In Python, non-local variables are read only by default and we must declare them explicitly as non-local in order to modify them.

Please find an example below of a nested function accessing a non-local variable.


In [2]:
def print_msg(msg):  # This is the outer enclosing function
    def printer():   # This is the nested function
        print(msg)   
    printer()
    
print_msg('Hello')


Hello

We can see that the nested function printer() was able to access the non-local variable msg of the enclosing function.

In the example above, what would happen if the last line of the function print_msg() returned the printer() function instead of calling it? This means the function was defined as follows:


In [3]:
def print_msg(msg):     # This is the outer enclosing function
    def printer():      # This is the nested function
        print(msg)
    return printer      # This is changed from the above example

another = print_msg("Hello")
another()


Hello

In the example above the print_msg() function was called with the string "Hello" and the returned function was bound to the name another. On calling another(), the message was still remembered although we have already finished executing the print_msg() function.

This technique by which some data ("Hello") get attached to the code is called closure in python.

This value in the enclosing scope is remembered even when the variable goes out of scope or the function itself is removed from the current namespace.


In [4]:
del print_msg
another()


Hello

The criteria that must be met to create closure in python

  • We must have a nested function (function inside a function)
  • Nested function must refer to value defined in the enclosing function.
  • The enclosing function must return the nested function.

Closures are good when:

  • To avoid global values and provide some form of data hiding.
  • To provide an object oriented solution to the problem.

When there are few methods (one method in most cases) to be implemented in a class, closures can provide an alternate and more elegant solutions. But when the number of attributes and methods get larger, better implement a class.


In [6]:
def make_multiplier_of(n):
    def multiplier(x):
        return x * n
    return multiplier

# multiplier of 3
times3 = make_multiplier_of(3)

# multiplier of 5
times5 = make_multiplier_of(5)

print(times3(9))

print(times5(3))

print(times5(times3(2)))


27
15
30

All function objects have a closureattribute that returns a tuple of cell objects if it is a closure function.


In [7]:
make_multiplier_of.__closure__
times3.__closure__


Out[7]:
(<cell at 0x107f20768: int object at 0x106075840>,)

In [8]:
times3.__closure__[0].cell_contents


Out[8]:
3

In [9]:
times5.__closure__[0].cell_contents


Out[9]:
5