Introduction to Clousure in Python:
Let's take a look at some python code.
Here we can see two functions, the outer_func() and inner_func(). In the outer_func() a variable is initialized with a string - 'Hello' and in the inner_func() we are printing it.
This piece of code contains a few things you might state as peculiarities that we will address in this article. For example, the nested function, the variable that was declared in the outer function being used by the inner function, and lastly, the outer function, returning a function just like we would return a variable. So let's understand each of these one by one, starting with the nested function.
Nested Function :
So when you're looking at the inner_func, you're actually looking at :
- The inner_func function itself
- The free variable message with the value 'Hello'
This combination of the inner_func function and the message variable is called the closure. We'll get to a more formal definition a little later.
With the understanding of nested functions, let's talk about this 'free' variable. We will also discuss the 'return inner_func()' statement, but a little later in the article.
Non-Local Variables in Nested Functions :
Every variable has a certain scope. The scope is essentially that part of the code, where the variable can be found and can also be accessed, if required.
In the above code, the second print statement i.e. the one outside the function will throw an error because the scope of the variable is only inside the function. It cannot be used outside of that function.
Going back to our earlier example, the scope of the variable 'message' is in the outer_func() and the nested function - inner_func(). Even if it is in two different scopes, it still references the same string object 'Hello'.
Python creates an intermediary object called 'cell' to achieve this.
Here, the memory address of the object 'cell' references a string - 'Hello'.
To prove this, let's print out the memory address of the string object in the outer function and also the closure.
In this code above, we're first creating two functions - pow2 and pow3 which are essentially calculating the square and cube of x. Then we create a func_dict, which is a dictionary of string to functions. When we call the functions, we're not calling them directly, we are using the keys that they are mapped to. Which means that functions can be used just like any other object in Python.
We have now cleared all basic concepts required to define closure.
Defining a Python Closure Function:
Closure in Python is an inner function object, a function that behaves like an object, that remembers and has access to variables in the local scope in which it was created even after the outer function has finished executing. It can also be defined as a means of binding data to a function (linking / attaching the data with the function so that they are together), without passing it as a parameter. Even if values in enclosing scopes are not present in memory, a closure is a function object that remembers those values, like the 'message' variable containing the string 'Hello', from our example.
A python closure isn't like a plain function. It allows the function to access those captured variables through the closure’s copies of their values or references, even if the function is invoked outside their scope.
Another way of relating to a python closure is :
Objects can be described as data with methods attached, while closures are functions with data attached.
From all that we read above, we can list out 3 characteristics of a python closure function :
- It is a nested function
- The closure will have access to a 'free' variable that is in outer scope
- It will be returned from the enclosing (outer) function.
- And, closure can be called a function object (as it is a function that behaves like an object) that is capable of remembering values that are in enclosing scopes (such as the outer functions) even if they are not present in memory.
The example of printing a greeting (our first example that printed 'Hello') was simple, let's take another example to get a better understanding.
We have a simple piece of code here that divides two numbers x and y. We're assuming that the user never inputs 0 for y.
How will we use this function divider now, since it contains a closure? We will call the divider function, and since we know that functions are first class objects, we'll store it in a variable.
These function calls to the divider function, create 3 closures. Each function (d1, d2, d3) will divide a number by 2, 5 or 4. Next, we will execute these closures.
Here, d1, d2 and d3 have different instances of the closure. The instance d1, will divide 20 by 2, as we initialized it with divider(2) and hence, for division, the value of y will be 2, and x = 20. d2 will divide 20 by 5 because it was initialized with divider(5), and the same way, d3 divides 20 by 4 as it was initialized with 5.
Let's see another example of a simple adder:
Here, as you must have spotted, our closure is the function f2. The function f2 takes the variable x that is the non local variable, and the variable y, which is passed to the function, and adds them (x + y).
The function 'adder' here is the same as the closure instances d1, d2, and d3 we created in the previous example. It was initialized with f1(12), which means that the variable x is now 12. Next, when we call adder(4), we are calling the inner function and the value of y becomes 4. The final result of adder(4) = 12 + 4 = 16.
When to use Python Closures and their benefits :
- Say you have a class that has two methods. In the Python programming language, a class always has the __init__ method. If you have just one more method besides it, an elegant solution would be to use a closure, instead of a class. Why? This increases readability of the code and even reduces work of the programmer. So, closures can be used to avoid the unnecessary use of class.
- Sometimes you might have variables in the global scope that are not used by more than one function. Instead of declaring the variables in global scope, you must think of using a closure. You can define them in the outer function and use them in the inner function. Closures can also be used to avoid the use of global scope.
- Have you thought about calling the inner function directly at any point of time? You cannot do it! The only way to access the inner function is by calling the outer function. Data hiding is an important use of closures.
Here's a summary of what we learnt from this article:
- The first and foremost note - Functions are first class objects.
- Python closure is essentially a nested function that has some data attached to it.
- The properties of a closure function are :
- It is a nested function
- It has access to a free variable in outer scope
- Will be returned from the outer function
- Is a function object (function that behaves like an object)
- Capable of remembering values present in outer functions (enclosing scopes)
- As we know that functions are first class objects, to use the closure, we need to initialize the variables with the outer functions. These variables are now instances of the closure function and can be called as functions themselves. Just like d1, d2, d3 in our example.
- Benefits of closure:
- Closures can be used to avoid the unnecessary use of class
- They can also be used to avoid the use of global scope
- Data hiding is an important use of closures.
That's it! You now know everything there is to closures.