Python Closures
Nonlocal variable in a nested function
Before defining a closure, we need to understand what a nested function and nonlocal variable are.
A nested function is a function defined inside another function. Nested functions can access variables of the enclosing scope.
In Python, the non-local variables are read-only by default, and we must declare them explicitly as non-local (using nonlocal keyword) to modify them.
In the following example, we will define a nested function accessing a non-local variable.
def show_msg(msg):
# This is the outer enclosing function
def show():
# This is the nested function
print(msg)
show()
# We execute the function
show_msg("Hello World")
Output
Hello World
As we can see above, the nested show()
function could access the non-local msg
variable of the enclosing function.
Defining a Closure Function
We will change the above example to see what will happen if the last line of the function show_msg()
returned the show()
function instead of calling it.
def show_msg(msg):
# This is the outer enclosing function
def show():
# This is the nested function
print(msg)
return show
# Here, we will try calling this function
another_func = show_msg("Hello World")
another_func()
Output
Hello World
The above output is unusual.
The show_msg()
function was called with the string Hello World
, and the returned function was bound to the name another_func
. When calling another_func()
, the message was memorized even after we had already finished executing the show_msh()
function.
This approach in which some data ("Hello World" in this case) becomes attached to the code is called closure in Python.
The value in the enclosing scope is memorized even when the variable goes out of the scope, or the function itself is removed from the current namespace.
In the following example, we will prove that the value in the enclosing scope is remembered even if the function is removed.
>>> del show_msg
>>> another_func()
Hello World
>>> show_msg("Hello World")
Traceback (most recent call last)
...
---> 15 print(next(gen))
...
NameError: name 'show_msg' is not defined
As we can see above, the returned function another_func()
still works even when the original function show_msg()
was deleted.
When do we have closures?
As seen from the above example, we speak about the closure in Python when a nested function references a value in its enclosing scope.
The criteria needed to create closure in Python can be given in the following points:
- We must have a nested function (function inside a function).
- The nested function must refer to a value defined in the enclosing function.
- The enclosing function must return the nested function.
When to use closures?
Now after understanding closure in Python and how to define them. We will see what closures are suitable for.
Closures help to avoid the use of global values and provide some data hiding. It can also be used as an object oriented solution to a problem.
Closures can provide an alternate and elegant solution when a class contains few methods (one method in most cases). However, when the number of attributes and methods gets larger, creating a new class is better.
In the following example, the use of closure can be preferable to defining a class and creating objects. But it is always up to you.
def make_addition_to(n):
def addition(x)
return x + n
return addition
# addition to 4
addition4 = make_addition_to(4)
# addition to 10
addition10 = make_addition_to(10)
# Output: 13
print(addition4(9))
# Output: 15
print(addition10(5))
# Output: 17
print(addition10(addition4(3)))
Output
13
15
17
We can also use Python Decorators to exploit closures.
We can found out the values that get enclosed in the closure function. All function objects have a __closure__
attribute that returns a tuple of cell objects if it is a closure function.
In the example above, we know addition4
, addition10
are closure functions.
>>> make_addition_to.__closure__
None
>>> addition10.__closure__
(<cell at 0x7fb8b1b21d10: int object at 0x5605d999fb20>)
The cell object has the attribute cell_contents
that stores the closed value.
>>> addition4.__closure__[0].cell_contents
4
>>> addition10.__closure__[0].cell_contents
10