Python Closures

Created with Sketch.

Python Closures

Summary: in this tutorial, you’ll learn about Python closures and their practical applications.

Introduction to the Python closures

In Python, you can define a function from the inside of another function. And this function is called a nested function. For example:

def say():
greeting = 'Hello'

def display():
print(greeting)

display()

Code language: Python (python)

In this example, we define the display function inside the say function. The display function is called a nested function.

Inside the display function, you access the greeting variable from its nonlocal scope.

Python calls the greeting variable a free variable.

When you look at the display function, you actually look at:

  • The display function itself.
  • And the free variable greeting with the value 'Hello'.

So the combination of the display function and greeting variable is called a closure:

By definition, a closure is a nested function that references one or more variables from its enclosing scope.

Returning an inner function

In Python, a function can return a value which is another function. For example:

def say():
greeting = 'Hello'

def display():
print(greeting)

return display

Code language: Python (python)

In this example, the say function returns the display function instead of executing it.

Also, when the say function returns the display function, it actually returns a closure:

The following assigns the return value of the say function to a variable fn. Since fn is a function, you can execute it:

fn = say()
fn()

Code language: Python (python)

Output:

Hello

 

The say function executes and returns a function. When the fn function executes, the say function already completes.

In other words, the scope of the say function was gone at the time the fn function executes.

Since the greeting variable belongs to the scope of the say function, it should also be destroyed with the scope of the function.

However, you still see that fn displays the value of the message variable.

Python cells and multi-scoped variables

The value of the greeting variable is shared between two scopes of:

  • The say function.
  • The closure

The label greeting is in two different scopes. However, they always reference the same string object with the value 'Hello'.

To achieve this, Python creates an intermediary object called a cell:

To find the memory address of the cell object, you can use the __closure__ property as follows:

print(fn.__closure__)

Code language: Python (python)

Output:

(<cell at 0x0000017184915C40: str object at 0x0000017186A829B0>,)

Code language: HTML, XML (xml)

The __closure__ returns a tuple of cells.

In this example, the memory address of the cell is 0x0000017184915C40. It references a string object at 0x0000017186A829B0.

If you display the memory address of the string object in the say function and closure, you should see that they reference the same object in the memory:

def say():
greeting = 'Hello'
print(hex(id(greeting)))

def display():
print(hex(id(greeting)))
print(greeting)

return display

fn = say()
fn()

Code language: Python (python)

Output:

0x17186a829b0
0x17186a829b0

 

When you access the value of the greeting variable, Python will technically “double-hop” to get the string value.

This explains why when the say() function was out of scope, you still can access the string object referenced by the greeting variable.

Based on this mechanism, you can think of a closure as a function and an extended scope that contains free variables.

To find the free variables that a closure contains, you can use the __code__.co_freevars, for example:

def say():

greeting = 'Hello'

def display():
print(greeting)

return display

fn = say()
print(fn.__code__.co_freevars)

Code language: Python (python)

Output:

('greeting',)

Code language: JavaScript (javascript)

In this example, the fn.__code__.co_freevars returns the greeting free variable of the fn closure.

When Python creates the closure

Python creates a new scope when a function executes. If that function creates a closure, Python also creates a new closure as well. Consider the following example:

First, define a function called multiplier that returns a closure:

def multiplier(x):
def multiply(y):
return x * y
return multiply

Code language: Python (python)

The multiplier function returns the multiplication of two arguments. However, it uses a closure instead.

Second, call the multiplier function three times:

m1 = multiplier(1)
m2 = multiplier(2)
m3 = multiplier(3)

Code language: Python (python)

These function calls create three closures. Each function multiplies a number with 1, 2, and 3.

Third, execute functions of the closures:

print(m1(10))
print(m2(10))
print(m3(10))

Code language: Python (python)

Output:

10
20
30

 

The m1, m2, and m3 have different instances of closure.

Python closures and for loop

Suppose that you want to create all three closures above at once and you might come up with the following:

multipliers = []
for x in range(1, 4):
multipliers.append(lambda y: x * y)

m1, m2, m3 = multipliers

print(m1(10))
print(m2(10))
print(m3(10))

Code language: PHP (php)

How it works.

  • First, declare a list that will store the closures.
  • Second, use a lambda expression to create a closure and append the closure to the list in each iteration.
  • Third, unpack the closures from the list to the m1, m2, and m3 variables.
  • Finally, pass the values 10, 20, and 30 to each closure and execute it.

The following shows the output:

30
30
30

 

This doesn’t work as you expected. But why?

The x starts from 1 to 3 in the loop. After the loop, its value is 3.

Each element of the list is the following closure:

lambda y: x*y

 

Python evaluates x when you call the m1(10), m2(10), and m3(10). At the moment the closures execute, x is 3.

That’s why you see the same result when you call m1(10), m2(10), and m3(10).

To fix this, you need to instruct Python to evaluate x in the loop:

def multiplier(x):
def multiply(y):
return x * y
return multiply

multipliers = []
for x in range(1, 4):
multipliers.append(multiplier(x))

m1, m2, m3 = multipliers

print(m1(10))
print(m2(10))
print(m3(10))

Code language: Python (python)

Summary

  • A closure is a function and an extended scope that contains free variables.

Leave a Reply

Your email address will not be published. Required fields are marked *