To understand python closure, you should have the idea of nested function and python class. Actually python closure is also a function that provides the opportunity to encapsulate some data with code.
Python nested function
1 2 3 4 5 6 7 8 9 10 |
def funcOut(): print("Now we are in funcOut.") def funcIn(): print("This function is defined inside the funcOut.nThis one is called a nested Function.") print("Here we will call the funcIn that is defined.") funcIn() print("We are in _main_.nCalling the funcOut.") funcOut() |
In the above code funcIn
is nested function inside the funcOut
. If you look at the output of the above code then you will understand the calling sequence of the functions. The output will be:
1 2 3 4 5 6 7 8 |
We are in _main_. Calling the funcOut. Now we are in funcOut. Here we will call the funcIn that is defined. This function is defined inside the funcOut. This one is called a nested Function. |
Turning the funcOut into a python closure
Say, you want to have all the functionalities that is done by the funcIn
from funcOut
.
How can you do this? What comes in your mind?
return right!!!
Normally we return a value or reference form a function. But here, we need to return the whole functionalities of funcIn
. If we just overwrite the function calling funcIn()
in line 6 by return funcIn
, then we have achieved what we want.
The thing that we have just done is known as closure in python. You will understand python closure more clearly as you go through the whole tutorial.
Idea of Python Closure
So, from the above we have learned that when a function returns another function defined in ( ie. nested function) it, it’s called a closure. Let’s now have a look on sample structure of a closure.
Python Closure Structure
1 2 3 4 5 6 7 8 9 |
def closureFunc(): def nestedFunc(): # ... statements ... print(" Welcome To Closure ") return nestedFunc get = closureFunc() get() |
This will output:
1 2 3 |
Welcome To Closure |
In the above code, as per the name of the function I hope you understand the outer function is the closure function, in which there is a nested function which is being returned by the closure function.
Python closure embeds data with code
When we create a object of a class, this object contain some information with it. Just like that closure embeds data with the code.
Let’s explore with an example code
1 2 3 4 5 6 7 8 9 10 |
def closureFunc(n): def nestedFunc(): # ... statements .. print("Welcome To Closure ") print("You have sent argument %d + 10 = %d" % (n, n+10)) return nestedFunc getting = closureFunc(12) getting() |
This will output:
1 2 3 4 |
Welcome To Closure You have sent argument 12 + 10 = 22 |
Notice line 7 and 8 – getting
variable is now working as a function. All the functionalities of the inner function of nested function is now being done by it.
Python Closure remembers its context
Look at the following code, we have deleted the closureFunc
.
1 2 3 4 5 6 7 8 9 10 11 |
def closureFunc(sum): def nestedFunc(): # ... statements .. print("Welcome To Closure ") print("You have sent argument %s" % sum) return nestedFunc getting = closureFunc(12) del closureFunc getting() |
This will output:
1 2 3 4 |
Welcome To Closure You have sent argument 12 |
This is the power of closure. Even if you delete the closure function the getting
remember its context where it was and what it does. That’s why we got the output of getting
even after deleting the actual function.
Use of nonlocal variable in closures
Let’s have a look to another example. The following closure adds up all the number upto a certain range which is given as argument to the closure function.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def closureFunc(up): val = 0 def nestedFunc(): nonlocal val print("Welcome To Closure ") for i in range(up+1): val += i print("Total is = %d" % val) return nestedFunc getting = closureFunc(5) getting() |
This will output:
1 2 3 4 |
Welcome To Closure Total is = 15 |
Notice that we have taken a variable val in the closureFunc and reuse it in the nestedFunc
declaring as a nonlocal to this function using the keyword nonlocal
.
If you do not declare as nonlocal then you will get error that local variable ‘val‘ referenced before assignment, that means it will be considered as a local variable to the nestedFunc
function .
Closure with argument
Let’s have a look at the last example of this tutorial. In this code we want to provide argument to the nestedFunc. And observe the output for different value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def closureFunc(up): val = 0 def nestedFunc(arg): nonlocal val print("Welcome To Closure ") for i in range(up+1): val += i val *= arg print("Total is = %d" % val) return nestedFunc retFunc = closureFunc(5) retFunc(10) retFunc(4) |
Below image shows the output of above python closure program.
If you can understand why the second output is 660 then I must say you have gained knowledge from this tutorial.
The output is 660 because, when line 11 executes, variable up=5 is set.
Then when line 12 executes, nestedFunc
executes and variable val=150 is set.
After that when we again call the function with different argument 4 in line 13, then the closureFunc is having up=5, val=150. So in the for loop val is updated by 150 plus summation of 1 to 5 that equals 150+15 = 165. Then multiply it with 4 which equals 660. That’s it. This is what python closure is. Hope this tutorial is helpful for you. Best of luck coding with closure.
__closure__
All function objects have a __closure__
tuple attribute that returns cell objects if it is a closure function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
def closureFunc(up): val = 0 def nestedFunc(arg): nonlocal val print("Welcome To Closure ") for i in range(up + 1): val += i val *= arg print("Total is = %d" % val) return nestedFunc retFunc = closureFunc(5) print(retFunc.__closure__) print(retFunc.__closure__[0].cell_contents) print(retFunc.__closure__[1].cell_contents) retFunc(10) print(retFunc.__closure__) print(retFunc.__closure__[0].cell_contents) print(retFunc.__closure__[1].cell_contents) retFunc(4) print(retFunc.__closure__) print(retFunc.__closure__[0].cell_contents) print(retFunc.__closure__[1].cell_contents) |
It will produce following output now and closure context values up and val are also getting printed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
(<cell at 0x10079f288: int object at 0x10028ba80>, <cell at 0x101033618: int object at 0x10028b9e0>) 5 Welcome To Closure Total is = 150 (<cell at 0x10079f288: int object at 0x10028ba80>, <cell at 0x101033618: int object at 0x10028cca0>) 5 150 Welcome To Closure Total is = 660 (<cell at 0x10079f288: int object at 0x10028ba80>, <cell at 0x101033618: int object at 0x1007eae70>) 5 660 |
Python closure is a good to know feature but it gets complicated if we have more inner functions and arguments. You can achieve the same thing with classes and normal functions. So use python closure with caution.
Reference: StackOverflow question