Introduction to Closures in Python

Understanding what, when & why to use closures!

Published in
4 min readMay 11, 2021

--

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

Whenever a function is executed, a new local namespace is created which represents the local environment that contains names of function parameters and variables assigned inside the function body. We can think of a namespace as a dictionary in which the keys are the object names and the values are the objects themselves.

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

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

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?

When there are few methods (one method in most cases) to be implemented in a class, closures can provide an alternate and more elegant solution. Also, closures and nested functions are especially useful if we want to write code based on the concept of lazy or delayed evaluation. Here is an example:

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:

Closure in Python can be defined when a nested function references a value in its enclosing scope. Closures provide some form of data hiding. A closure can also be a highly efficient way to preserve state across a series of function calls. To create a closure function in Python:

  • 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.

--

--

Sports Enthusiast | Senior Deep Learning Engineer. Python Blogger @ medium. Background in Machine Learning & Python. Linux and Vim Fan