The aim of this article is to give you an intuition and understanding of closures. We will be looking at what closures are, how they work, look the prequisite knowledge in order to understand closures and a practical example.
Before we get into any of it I want you to think about a question.
How can we write a function that outputs:
1. An addition between two numbers a & b
2. A count of how many times the function has run
What is a Closure?
Explained in a very non philosophical way:
“A closure is a function plus an extended scope that contains the free variables”.
Let us have a first glance below:
Before we move on let’s get a minimum understanding of Scopes and Free Variables.
About python scope: A scope is the “space” where objects such as variables functions, etc. exists in the code. What do I mean by that… let’s look at the picture below.
As you can see in the picture above when we create variables or objects they become part of a scope.
- Variables inside functions exist in “Local Scope” unless specified otherwise such as variable “a”;
- Variable “b” is a global variable because it’s included in the “Global Scope”. A global scope is the scope of the module or simply put the python file Eg: my_code.py ;
- Finally we have the “Builtin Scope” where objects / functions like “print” , “len”, True or False… etc. exist.
- Global Variables: We can access a global variable in a local scope using “global” syntax. In the below example a is not defined inside my_func if not for the global syntax this would result in an error. We can now manipulate a variable Eg: a that belongs to Global Scope in the local scope.
- Nonlocal Variables: Allowes us to manipulate variables belonging to another scope inside closures aka nested functions. For example we take the variable a from the scope of func inner and modify it in scope of func outer.
How Scopes work:
Python starts looking for variable/objects starting with local scope (if not instructed otherwise — global, nonlocal) if object can’t be found it expands the search to global scope, then builtin scope and finally throws an error if the search was unsuccesfull.
Now, an important aspect we need to know about scopes especially if we think of local scopes is that they have a short life. Once the object has been created the scope is deleted, and the memory is freed.
Python stores the relationship between scopes and variables in “namespaces”. You can think of namespaces as tables. Each scope get’s it’s own namespace.
What is a free variable?
A free variable is a variable used in a scope Eg: inner but defined in another scope Eg: outer. Let’s look at an example:
The “s” variable is defined in the local scope of “Outer” function. However the “inner” function gets to use the function “s” in the “inner” local scope despite the fact that “s” is not part of the “inner” local scope.
Back to closures:
Let’s look at the first example once more and this time call the function.
How does it all come together?
- The inner function is evaluated first, a local scope is created along with a namespace.
- When the inner function is evaluated, python looks for variable s and because it can’t find it in the local scope of inner function, it becomes aware that the s variable is nonlocal but it is not yet looking for it untill the function is run.
- The outer function is evaluated second, creates the scope and namsepace and the s variable is defined.
- When the inner function is run, the s variable is not found in local scope but in the scope of the “outer” function.
- The result of inner function is passed to the “outer” function which get’s returned and all is well… Or is it?
Now the problem is that we said earlier that a the scope of the function is removed after the function is run.
This means that after outer function is run the variable s is removed along with the local scope of “outer” function and the only object that is returned by outer funtion is the inner function. We should no longer be able to access the s variable. But we do! because:
The function outer returns function inner plus variable s. And tha is what a closure is.
How are closures useful in practice?
With our newly acquired powers we can now write a high order function (takes a function and arguments) and output the result and how many times the function has run.
Thanks to the magic of Closures we can use the free variable to count how many times the function has run.
Equally we could write a function that adds two numbers and retains the total of all the numbers ever added.
We could write a function that remembers all the input variables ever received.
I hope you have now a better understanding of what closures are what they do and why are they useful.