Python Closures


August 23, 2021, Learn eTutorial
1272

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.

Nested Function with Non Local Variables

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:

Calling Sequence of nested function

Calling Sequence of nested function

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.

Non Local Variables

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.

Non Local Variables

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:

Non Local Variables

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.
  • Since msg is declared inside an enclosed function ,here OuterFunction(), 'msg' is a local variable to its enclosed function.
  • However you can see that 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 .

Example: Local Variable Vs NonLocal Variable

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.

How to modify a non-local variable?

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.

Example: NonLocal Variable

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.

Closures in python

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.

Python Closure Strucuture

Closure Strucuture

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

Important features of Closures

Closures binds the data with code

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().

Example: Closure

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.

Closures remembers its context

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.

Example: Closure

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.

Example: Closure

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.

Closure with argument

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.

Example: Finding nth power of a number using closures

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.

Benefits of using Closures

  • Closures are perfect alternatives of class, especially when classes have single methods or few methods.
  • Closure helps to reduce the use of global variables
  • Closures are used as a callback function thereby providing data hiding to some extent.
  • Closures are used to replace hard coded constants
  • Closures are extensively used by python decorators about which you will learn in the coming tutorial.
  • Closures are applied to solve mathematical functions like composition, partial application , currying etc.