In this python tutorial you will learn everything about Closures, one of the important tools in functional programming . You will see what closures are in python, how they are implemented and the benefits of using closures. At first you will grasp an idea on nested function and non local variables which are basic constructs for implementing closures.
A nested function in simple terms, is a function defined inside another function. The functions defined inside are known as Inner functions and the function which encloses the inner function is known as Outer function.
Following is an example of a nested function
def OuterFunction(msg):
def Innerfunction():
print(msg)
InnerFunction()
OuterFunction('Welcome to Learn eTutorials')
In the above code snippet, we have defined an OuterFunction
which takes some text or message , here msg
, as the argument. Also we have defined another function inside the OuterFunction
and named it as InnerFunction
. The InnerFunction
prints the msg
which we have passed as an argument in the OuterFunction
. Finally we have called the inner function from the scope of OuterFunction
. In essence, the OuterFunction
encloses the InnerFunction
and hence is also known as the Enclosing function and the inner function is local to the outer function hence is also called as Nested function.
The calling sequence of the above code always starts with the main function where it calls OuterFunction(‘Learn eTutorial!!!’)
which shifts the control to the function OuterFunction(msg)
where it encounters the function call InnerFunction()
. The function calling sequence can be best visualized as follows:
When a function is declared inside another function then there takes place nesting of functions. Nesting can be done to any level but increasing the level ends up in high complexity.
In the above code ‘msg’ is the only variable declared. From our previous we know that variables can be either declared globally or locally in a function based on the scope and lifetime of variables present in that function or program. variables having life throughout the program are referred as global variables and those variables whose scope is limited within a function are known as local variables. But there exists a third variant called non-local variables which mainly works with nested functions in python.
When a variable is defined inside an enclosed function it is said to have non-local scope to its nested function. Such types of variables are known as non-local variables. Non local variables are accessible to the function in which it is defined and to all its nested functions.
The scope of non local variable for the previous code snippet can be best visualised as follows:
Here, variable msg
is the only variable declared in this program. Is msg
a global or local or non-local variable?.
msg
is not a global variable because it is not declared in the main function and does not have a global keyword.msg
is declared inside an enclosed function ,here OuterFunction()
, 'msg' is a local variable to its enclosed function.msg
is accessible to the nested function InnerFunction()
. So can we claim that 'msg' is a local variable? The answer is NO. Here 'msg' is a non-local variable to its nested function.Now let's see how we can differentiate local variables from non-local variables by making some changes to our previous code snippet .
def OuterFunction():
msg = 'Welcome to Learn eTutorials' #Local Variable
def InnerFunction(): # Nested Function
msg = 'PYTHON' # Non Local Variable
print('Inner: ',msg)
InnerFunction()
print('Outer: ',x)
OuterFunction() # Main Function
Keeping in mind the fact that local variables of enclosed functions are non local to their inner functions, any changes made to 'msg' in the inner function does not reflect on 'msg' in the outer function.
Output:
Inner: PYTHON Outer: Welcome to Learn eTutorials
From the above output it is clear that function InnerFunction() can access the variable 'msg' but it cannot change them. The inner function has just reassigned the variable with new value only. This means a new local variable with the same name has been created in the nested function.
To modify a non-local variable we must declare it explicitly with the keyword nonlocal. By this way we can easily identify a non-local variable. Lets see the significance of non-local keywords and what changes will happen to our previous code if we use them.
def OuterFunction(): #Enclosed Function
msg = 'Welcome to Learn eTutorials' #Local Variable
def InnerFunction(): # Nested Function
nonlocal msg
msg = 'PYTHON' # Non Local Variable
print('Inner: ',msg)
InnerFunction()
OuterFunction() # Main Function
Here, in this example we have explicitly declared msg with keyword non-local in inner function InnerFunction() and is assigned with a string value. The modification made to msg in nested function reflected in enclosed function too.
Output:
Inner: PYTHON Outer: PYTHON
From the output it is pretty clear that variable msg is neither a global variable nor a local variable.
Now we can learn about closures in python. For that we can consider our previous example of the nested function and let's see the conversion of the nested function to a closure.
In order to transform a nested function to a closure we simply need to return the nested function without the parentheses . The structure of closure function is as shown below.
When the return value of a function depends on the values of one or more variables which are declared outside the function, then that function is said to be a closure function.In the above example, the return value of InnerFunction depends on the ‘msg’ variable which is declared outside the InnerFunction
The first and foremost feature of closures in python is that closures encapsulate the data with code. The fact behind the name of closure is that it binds the non local data in inner function code.
In the above code the return InnerFunction statement is not merely returning any value; instead it is returning the function itself when the OuterFunction is called. So there arises the need of variable declaration to store this function.
Here in our example, we have declared a new variable get
which stores the InnerFunction
returned by the OuterFunciton
. Since get
contains the function we can use get
as a function and can call the function as get(). As the InnerFunction does not have any argument we don’t need to pass any arguments in get().
def OuterFunction(msg):
def InnerFunction():
print('Inner: ',msg)
return InnerFunction
get = OuterFunction('Welcome to Learn eTutorials')
print(get)
print(get())
Now when we run the above code we would get the below output:
Output:
.InnerFunction at 0x01DF6148> Inner: Welcome to Learn eTutorials None
From the output it is clear that get() works as a function and prints the data binded to the code.
A closure can be defined as a function object which memorizes the non-local variables in the enclosing scope regardless of its absence in memory.
def OuterFunction(msg):
def InnerFunction():
print('Inner: ',msg)
return InnerFunction
get = OuterFunction('Welcome to Learn eTutorials')
del OuterFunction
OuterFunction('Welcome to Learn eTutorials')
In this code the OuterFunction is the enclosing scope which is deleted using the keyword del. Soon after deletion ,we try to call the OuterFunction again. What would be the output?
Obviously , the python compiler will raise a Name Error stating that OuterFunction is not defined as shown below.
Output Error:
OuterFunction('Welcome to Learn eTutorials') NameError: name 'OuterFunction' is not defined
Here comes the power of closure with its magic memorizing ability.
def OuterFunction(msg):
def InnerFunction():
print('Inner: ',msg)
return InnerFunction
get = OuterFunction('Welcome to Learn eTutorials')
del OuterFunction
get()
Output:
Inner: Welcome to Learn eTutorials
This means that the variable ‘get’ memorises some state of inner function even if the outer function is deleted.
To understand about closures in better ways let us go through the one more example which provides an argument to the InnerFuction. So far we have not passed any argument to InnerFunction.
def n_pow(exp):
def pow_of(base):
return pow(base,exp)
return pow_of
sec
print(second_pow(3))
print(second_pow(4))
print(second_pow(5))
print("\n")
third_pow = n_pow(3)
print(third_pow(3))
print(third_pow(4))
print(third_pow(5))
print("\n")
fifth_pow =n_pow(5)
print(fifth_pow(2))
print(fifth_pow(3))
Output:
9 16 25 27 64 125 32 243
In this example, we have an outer function named n_pow
which passes an argument called exp
. Also we have defined a function pow_of
inside the n_pow
which also takes an argument base
. The pow_of
function is the local or nested function of the outer function n_pow
and it returns the power of whatever argument we passed here. Important point to be noted is that the argument exp is in the enclosing scope. Finally the outer function returns the inner function without parentheses which makes that function as a closure.
The variable second_pow
stores the state of inner function on function call and 2 is passed as the argument . Now the value of exponent becomes 2. So whenever we call the second_pow
function for any number it will produce the square of that base number. Here second_pow(3)
passes 3 as the argument to base in the inner function and function returns the square of 3 i.e 9. Similarly the third_pow
variables on function call will give the cube of the specified numbers as shown in example.