Closures in Python

A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory. 


Closures are elegant Python constructs. In this article, we’ll learn about them, how to define a closure, why and when to use them.

But before getting into what a closure is, we have to first understand what a nested function is and how scoping rules work for them. So let’s get started.

Scoping Rules and Nested Functions in Python

When resolving names, the interpreter first searches the local namespace. If no match exists, it searches the global namespace, which is the module in which function is defined. If no match is still not found, it finally checks the built-in namespace before raising the NameError exception. This is illustrated in below figure:

Namespace search order by Python Interpreter

Let’s consider the below example:

age = 27
def birthday():
age = 28
birthday()
print(age) # age will still be 27
>>
27

When variables are assigned inside a function, they’re always bound to the function’s local namespace; as a result, the variable age in the function body refers to an entirely new object containing the value 28, not the outer variable. This behavior can be altered using the global statement. Below example highlights that:

age = 27
name = "Sarah"
def birthday():
global age # 'age' is in global namespace
age = 28
name = "Roark"
birthday() # age is now 28. name will still be "Sarah".

Python also supports nested function definitions (function inside a function). Here’s an example:

def countdown(start):
# This is the outer enclosing function
def display():
# This is the nested function
n = start
while n > 0:
n-=1
print('T-minus %d' % n)

display()
# We execute the function
countdown(3)
>>>
T-minus 3
T-minus 2
T-minus 1

Defining a Closure Function

def countdown(start):
# This is the outer enclosing function
def display():
# This is the nested function
n = start
while n > 0:
n-=1
print('T-minus %d' % n)
return display# Now let's try calling this function.
counter1 = countdown(2)
counter1()
>>>
T-minus 2
T-minus 1

The countdown() function was called with the value 2 and the returned function was bound to the name counter1. when counter1() is executed, it uses the value of start that was originally supplied to countdown(). Thus, On calling counter1(), the value was still remembered although we had already finished executing the countdown() function.

This technique by which some data (2 in this case) gets 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. We can try the following code to confirm that:

>>> del countdown>>> counter1()
T-minus 2
T-minus 1
>>> countdown(2)
Traceback (most recent call last):
...
NameError: name 'countdown' is not defined

When to use closures?

from urllib.request import urlopendef page(url): 
def get():
return urlopen(url).read()
return get

In the above example, the page() function doesn’t actually carry out any computation. Instead, it merely creates and returns a function get() that will fetch the contents of a web page when it is called. Thus, the computation carried out in get() is actually delayed until some later point in a program when get()is evaluated. For example:

>>> url1 = page("http://www.google.com") 
>>> url2 = page("http://www.bing.com")
>>> url1
<function page.<locals>.get at 0x10a6054d0>
>>> url2
<function page.<locals>.get at 0x10a6055f0>

>>> gdata = url1() # Fetches http://www.google.com
>>> bdata = url2() # Fetches http://www.bing.com
>>>

The values that get enclosed in the closure function can be found out.

All function objects have a __closure__ attribute that returns a tuple of cell objects if it is a closure function. Referring to the example above, we know url1 and url2 are closure functions.

>>> page.__closure__       # Returns None since not a closure
>>> url1.__closure__
(<cell at 0x10a5f1250: str object at 0x10a5f3120>,)

The cell object has the attribute cell_contents which stores the closed value.

>>> url1.__closure__[0].cell_contents
'http://www.google.com'
>>> url2.__closure__[0].cell_contents
'http://www.bing.com'

Conclusions:

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

Comments

Popular posts from this blog

Read and Navigate XML - Beautiful Soup

difference-between-stream-processing-and-message-processing

WordNet in Python