Closures simplified with Python

Aniket Mishra
5 min readDec 20, 2018

--

When people hear the word ‘Closure’ in programming, they think it has to do something with web development, especially JavaScript. But closures are not limited to JavaScript. They can be used in any language that supports first-class functions.

Closures are quite confusing entities in any programming language that supports them. It takes a while just to understand what they are, let alone use them to solve problems, but once understood, they can become a very powerful tool to use. You can log your functions, check how long they took to run, override variables from outer functions and a lot more.

But what exactly is a closure?

Well, simply put, a closure is an inner function that has access to variables created in an outer function to a degree by which it can store and manipulate it even after the outer function has finished executing.

Confused? Oh well, most of us are. But you see, Python has a way of making things simple. Isn’t that why we all love it so much?

But before we dig into Closures, there are a few things we need to know. Nested functions, first-class functions and variable scoping to name a few. Once we understand how closures work, it will be easier to explain what they exactly are.

Nested Functions

A nested function is a function that is defined inside another function knows as the enclosing function. Since Python follows the general variable scoping rules, the enclosed function is invisible to everything outside the enclosing function but it can access all the variables and functions declared in all the enclosing functions above it. Functions can be nested an infinite number of times but to make the code readable and to avoid a pyramid of doom, only a few nesting levels are usually made. However, the inner functions are freshly redefined every time the outer function is called and are deleted from memory once the outer function finishes executing.

def outer_function():
outer_variable = 'Outer Variable'
def inner_function():
inner_variable = 'Inner Variable'
print('Inner Variable : {}'.format(inner_variable))
print('Outer Variable : {}'.format(outer_variable))
inner_function()
outer_function()

The Expected output of the program would be,

Inner Variable : Inner Variable
Outer Variable : Outer Variable

First-class Functions

A programming language supports first-class functions if it can use functions as arguments to other functions, use them as return values, and even assigning those functions to a variable or store them in different data structures. It is important to note that a programming language that supports first-class functions gives no special status to the name of the function. Instead, it treats them like ordinary variables with a function type.

Let’s break that down.

First-Class Functions are functions which are treated like First Class Citizen. These, in a programming language, are objects which can be:

  • created at runtime
  • used as parameters to a function
  • used as a return value from a function
  • assigned to variables (just by using the function name without the ‘()’)
  • stored in data structures such as hash tables, lists and so on.

Simply put, they are variables of function type as mentioned above. These can do everything a normal variable can. And since they are functions, they can do a lot more.

def square_function(side): return side*side
def cube_function(side): return side*side*side
def output_function(side, argument_function):
return argument_function(side)
#Assign the functions without executing them
square_variable = square_function

cube_variable = cube_function
output_variable = output_function(10,square_variable)print(output_variable)
output_variable = output_function(10, cube_variable)
print(output_variable)

The expected output of the above program is

100
1000

Now, let’s break the program down.

Here, the function output_function acts as a higher-order function as it takes a function as an argument and returns a function as well.

Next, we store the functions square_function and cube_function in the variables square_variable and cube_variable respectively. This is an example of what we can do with first-class functions. Now the variables point to the location where the functions are and will act like one.

Now we create the output_variable and call the output_function which takes the parameters of 10 and square_variable/cube_variable. These variables call the refer the functions and the output_function then run them.

Finally, the output is stored in the output_variable and is then printed.

Variable Scope and Lifespan

A diagram showing the scope of variables based on where they are defined.

Scoping is a simple topic but is easy to forget about.

Built-in Variables: Built-in variables are predefined names that contain a constant value and can not be changed. (They can be but it’s not advisable to do so.)

Global Variables: These are declared on the top of a program and can be accessed by all other data structures in that program.

Enclosing Variables: As the name suggests, variables that are enclosed inside a block of code is enclosed inside it and can only be accessed by that block or the blocks declared inside it.

Local Variables: Local variables are those which are declared inside a block and exist only inside that boundary. They can not be accessed by any other blocks.

Here’s a code example:

global = 1def outer_function(x):
outer_var = x
print('Outer_var = {}'.format(outer_var))
print('Global is accessed here too and is {}.format(global)')
def inner_function():
inner_var = 100
print('outer_var is {} and is enclosing.'.format(outer_var))
print('inner_var is {} '.format(inner_var)'
print('Global is called in inner and is {}'.format(global))
#print(inner_var) will provide an error as inner_var is deleted once the inner_function has finished executing.

#calling outer_function
outer_function(10)

Output:

Outer_vat = 10
Global is accessed here too and is 1
outer_var is 10 and is enclosing.
inner_var is 100
Global is called in inner and is 1

Finally, Closures!

A closure closes over the free variables in its environment. (If a variable is used in a code block but not defined there, it is a free variable.)

Here’s another code:

def outer_function(parameter):
outer_var = parameter

def inner_function():
print('Outer Variable in inner function: {}'.format(outer_var))

#Note, there is no '()' in the return statement
#The inner_function is being returned and not being called
return inner_function
if __name__ == '__main__': caller = outer_function('Ahoy Matey!')
caller()

Output :

Outer Variable in inner function: Ahoy Matey!

Let’s break that code down.

The outer_function takes in an argument and then defines the inner_function which uses outer_function’s variable. The print statement in the inner_function is not executed when the inner_function is returned because when it was returned without the parenthesis which means it wasn’t called.

Then, in the main function, the variable ‘caller’ stores the return value of outer_function. Since outer_function returns a function, ‘caller’ becomes a function (versatility of python).

At this point, the outer_function has finished executing, ended its scope, and has been deleted from memory.

So, when ‘caller’ is called, it acts as the inner_function and executes it. It prints the value that was taken in the outer_function which has been removed from memory. Therein lies the beauty of closures. We can access those values.

So, here’s what closures truly are…

They are inner functions that can access and modify the variables of an enclosing function and can hold that value in order to use them after the enclosing function has been executed and the variables local to it have been removed from memory.

Thank you.

--

--

Aniket Mishra

Core Team and Developer @Codezoned; Plays around with data and builds websites. Usually trying to perform magic tricks with the computer.