All You Need to Know About Closures in Python.

Article Agenda

In this article, I will explain you the concept of Closures in Python. More specifically, we will explore the following:

Introduction

Now, there's a caveat to this post and that is: You must have a knowledge of First Class Functions. If you don't know about this, then no need to worry. I got your back. Just view my article on First Class Function or if you are more of an observational learner, then you may go ahead and watch the video version of First Class Function as well.

Psst.. If you are in a hurry, you can watch the video version of this post instead

Alright, let's get started.

Nested Functions

def calculation(number):
    def double(no):
        return no*2

    return double(number)

calculation(10)               # 20

In the above code, I have a function calculation() which takes in a number as argument. Inside it, I have declared another function double() which also takes in an argument no and then returns the double of no. Finally, I exited from the calculation() function by making a call to the double() function.

Now the above scenario was a classical example of a nested function. Let's understand each step briefly:

  1. A call is made to calculation() with parameter value 10.
  2. Interpreter jumps inside calculation() and finds a return statement with call to another function double() with the same parameter number = 10. Current state is paused.
  3. Interpreter jumps inside double() with parameter no = 10 and then returns the double of no which is 20. Exits the double() function.
  4. 20 gets reflected and the paused state is resumed. This results in us finally getting the value 20.

Great! See, how easy it was? Okay, lets cover a scenario where you have multiple nested functions inside a parent function. In this case, all those functions will be executed whose call have been made in the parent function's return statement. The following example demonstrates it:

def calculation(number):
    def double(no):           # executed
        return no*2

    def triple(no):           # skipped
        return no*3

    def quadruple(no):        # executed
        return no*4

    return double(number), quadruple(number)

calculation(10)               # (20, 40) <-- this is a tuple

Very well. Now this was all about Nested Functions. Now, what if I tell you that there is a neater way of writing the nested functions? There exists a more professional and a cleaner way of writing nested functions and that way makes use of a property called Closure.

Closure Property

First of all, let's see what an enclosing scope means:

def parent():
    def child()
        pass
    pass

In the above code, parent is the enclosing scope for child. Which means that child will only be invoked if parent makes a call for it. Otherwise, child will stay dormant.

Now, let's see what does "values inside the enclosing scope" mean:

outside1 = "Outside parent func"

def parent(msg):

    inside1 = "I am inside parent"

    def child()
        pass

    inside2 = "I am inside parent"

    pass

outside2 = "Outside parent func"

parent("Hello there!")

In the above code, outside1 and outside2 are not inside parent() therefore, they are not inside the enclosing scope of child(). On the other hand, inside1 and inside2 are inside parent() function, this means that they are within the enclosing scope of child().

Similarly, can you find one more value (or variable) which is inside the enclosing scope of child()? Yes, the msg variable. Since we can use msg inside the parent() function, we can successfully call it to be in the enclosing scope of child().

Finally, let's understand what does "remembers the value in enclosing scope" mean. Let's take our previous example and move on from there:

def calculation(number):

    number2 = 30             

    def double(no):
        return no*2

    number3 = 50

    return double(number)

calculation(10)               # 20

I have added two extra variables as well, for better understanding. Okay, so here, as per our previous observations, we can quickly conclude that, number, number2, number3 are in the enclosing scope of double(). Now, let me remind you, closure property states that:

function objects can remember values in its enclosing scopes and these values can be used within that function.

This means that all these variables in the enclosing scope of double() can easily be accessed inside double and be used in any way you want without even passing them as the parameters to double() function. Didn't get this? Check out the valid snippet below:

def calculation(number):

    number2 = 30             

    def double():
        print(f"I can remember: {number},{number2},{number3}")

    number3 = 50

    return double()

calculation(10)               # I can remember: 10,30,50

Notice that how double() can access and modify number, number2, number3 without them being passed as arguments. Well, this is the due to it being able to "remember" the values in its enclosing scope. This was the great story of Closure.

Now, before ending this article, lets refactor our previous cluttered code that we wrote without closures. You would be seeing a significant difference in the style.

def calculation(number):
    def double():           # executed
        return number*2

    def triple():           # skipped
        return number*3

    def quadruple():        # executed
        return number*4

    return double(), quadruple()

calculation(10)               # (20, 40) <-- this is a tuple

Looks neat ain't it? No tossing around of parameters and not even explicitly passing them. The code looks way more cleaner and professional.

Would You Like to Support Me?

If you want to support me and my contents, then go ahead and consider doing it. I would highly appreciate that:

11