In this article, we will learn what python closure is, how to use it and what it can be used for. Also, we will explain some extra terms like nonlocal and free variables, what a scope for variables is and what nested and first-class functions are.
First, let’s look at the definition from Wikipedia:
(…) closure, (…) is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment. The environment is a mapping associating each free variable of the function (variables that are used locally but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created. Unlike a plain function, a closure allows the function to access those captured variables through the closure’s copies of their values or references, even when the function is invoked outside their scope.
It might be a bit difficult to understand for now so let’s explain some terms and look at the examples.
Function that is defined inside another function is called nested function. For example:
def outer(variable): var = variable def nested(): print(var) nested() outer(1) # 1
In Python, the variable can be accessed from inside the scope that it was created. In the above example, we can call the nested function only inside the outer function. The same with
var variable, we can’t call it outside
In Python, functions are first class functions. It means that functions can be treated as objects. So, for example, it can be passed as an argument of another function, can be assigned or returned.
Closure example compared to a class-based example
Let’s say we want to get the maximum number from the collection to which we can add elements.
Class based example
class Maximum(): def __init__(self): self.collection =  def __call__(self, new_value): self.collection.append(new_value) return max(self.collection) maximum = Maximum() print(maximum(1)) # 1 print(maximum(3)) # 3 print(maximum(2)) # 3
def make_maximum(): collection =  def maximum(new_value): collection.append(new_value) return max(collection) return maximum maximum = make_maximum() print(maximum(1)) # 1 print(maximum(3)) # 3 print(maximum(2)) # 3
As we can see here both examples are very similar. It is obvious where class
collection. It is an attribute of its instance. But where closure keeps
collection? While we call
maximum, we are long after the execution of
make_maximum. Note that
collection is a local variable of
make_maximum, we initialize it thereby
= . We can even extend this example by removing
make_maximum and it will still work.
maximum = make_maximum() del make_maximum print(maximum(1)) # 1 print(maximum(3)) # 3 print(maximum(2)) # 3
So how can we access
collection? Well, it is a free variable. It means that it is not bound to its local scope. Closure keeps binding to a free variable that exists when the function is defined and it can be used later even when
make_maximum no longer exists.
Our example code can be easily refactored. Every time that we add a new variable to
collection, we get the max value of it.
max function is O(n) time complexity because it always goes through all elements to get it. We can store max value of the previous state and then just compare it with the new variable and return a higher number. It can look like this:
def make_maximum(): highest = 0 def maximum(new_value): highest = new_value if new_value > highest else highest return highest return maximum maximum = make_maximum() print(maximum(1)) print(maximum(3)) print(maximum(2))
highest and it should work fine. Well, not so quickly, it throws us an error.
UnboundLocalError: local variable 'highest' referenced before assignment
The problem here is that previously we had a mutable type and we just
append to it. Now it is an immutable type. So
highest = new_value or
highest = highest actually makes a new variable with local scope. It is not a free variable anymore.
To fix this we need to use a nonlocal keyword. It makes a variable a free variable and allows us to change the immutable values stored in closure. A correct example should look like this:
def make_maximum(): highest = 0 def maximum(new_value): nonlocal highest highest = new_value if new_value > highest else highest return highest return maximum maximum = make_maximum() print(maximum(1)) # 1 print(maximum(3)) # 3 print(maximum(2)) # 3
Requirements for closure
There are three requirements to make closure:
- We need to have a nested function
- We need to have access to a free variable (it is a collection variable in our example).
- We need to return a nested function.
When closures can be used
We can find some cases when closures might be a better choice
- When we want to reduce the use of global variables. It is some kind of data hiding.
- When we have some simple functions to implement. With more complex tasks it is better to go with classes and OOP.
Let me provide simple example here
elements = list(range(20)) def stepper_of(n): def every(l): return l[::n] return every every_second = stepper_of(2) every_third = stepper_of(3) print(every_second(elements)) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] print(every_third(elements)) # [0, 3, 6, 9, 12, 15, 18] print(every_third(every_second(elements))) # [0, 6, 12, 18]
What if someone asked us to get
highest number without calling it? There is a way to get values encolsed by closure. We need to use
__closure__ attribute. It returns tuple of objects. For us:
print(maximum.__closure__) # (<cell at 0x7fe3fdb9b5b8: int object at 0x55f6a5f0b040>,)
and to get it we need to add
cell_contents to get its value
print(maximum.__closure__.cell_contents) # 3