In this tutorial, you’ll learn about Python closure, how to define a closure, and the reasons you should use it.
What are Closures in Python?
Like nested loops, we can also nest functions. That said, Python gives us the power to define functions within functions.
Python Closures are these inner functions that are enclosed within the outer function. Closures can access variables present in the outer function scope. It can access these variables even after the outer function has completed its execution.
To get a better understanding of closures, let’s first see how scope works in nested functions.
Nonlocal variable in a nested function
Before getting into what a closure is, we have to first understand what a nested function and nonlocal variable is.
A nested function is one that is defined inside another function. The variables of the enclosing scope can be accessed by nested functions.
These non-local variables are read-only by default in Python, and we must explicitly declare them as non-local (with the nonlocal keyword) to edit them.
Following is an example of a nested function accessing a non-local variable.
def print_msg(msg): # This is the outer enclosing function def printer(): # This is the nested function print(msg) printer() # We execute the function # Output: Hello print_msg("Hello")
We can see that the nested printer() function was able to access the enclosing function’s non-local message variable.
Defining a Closure Function
What if the last line of the method print msg() returned the printer() function instead of calling it in the previous example? This indicates that the function was written as follows:
def print_msg(msg): # This is the outer enclosing function def printer(): # This is the nested function print(msg) return printer # returns the nested function # Now let's try calling this function. # Output: Hello another = print_msg("Hello") another()
With the string “Hello,” the print msg() function was run, and the returned function was bound to the name another. Even though we had already completed the print msg() method, the message was remembered when we called another().
This technique by which some data (
"Hello in this case) gets attached to the code is called closure in Python.
Even if the variable goes out of scope or the function is removed from the current namespace, the value in the surrounding scope is remembered.
Try running the following in the Python shell to see the output.
>>> del print_msg >>> another() Hello >>> print_msg("Hello") Traceback (most recent call last): ... NameError: name 'print_msg' is not defined
Here, the returned function still works even when the original function was deleted.
When do we have closures?
As seen from the above example, we have a closure in Python when a nested function references a value in its enclosing scope.
The criteria that must be met to create closure in Python are summarized 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?
So what are closures good for?
Closures can avoid the use of global values and provides some form of data hiding. It can also provide an object oriented solution to the problem.
Closures can give an alternative and more elegant way when there are only a few methods to implement in a class (usually just one). When the number of characteristics and methods grows, it’s preferable to create a class.
Here is a simple example where a closure might be more preferable than defining a class and making objects. But the preference is all yours.
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) # Output: 27 print(times3(9)) # Output: 15 print(times5(3)) # Output: 30 print(times5(times3(2)))
27 15 30
Python Decorators make an extensive use of closures as well.
On a concluding note, it is good to point out that the values that get enclosed in the closure function can be found out.
If a function is a closure function, it has a closure attribute that returns a tuple of cell objects. We know times3 and times5 are closure functions from the previous example.
>>> make_multiplier_of.__closure__ >>> times3.__closure__ (<cell at 0x0000000002D155B8: int object at 0x000000001E39B6E0>,)
The cell object has the attribute cell_contents which stores the closed value.
>>> times3.__closure__.cell_contents 3 >>> times5.__closure__.cell_contents 5