1. Home
  2. Docs
  3. Python 201
  4. Closures in Python

Closures in Python

 

In this lesson you will learn about Closures in Python.

Knowledge of closures important to understand

decorators, which we will cover in the next lesson.

To understand closures, first we have to learn about

nested functions.

So Let’s start from ground up, starting from functions.

 

Functions are objects.

 
As you know, in Python everything is an object,

so functions are also objects.

We can assign them to a variable as like, int, string, etc.

Let’s consider a simple function ‘func’
  

def func():

    print("Simple function")

 
 

if we call func() it will print “Simple function”

We can assign it to a variable
  

f = func

 
 
and we call the function by using () with ‘f’ variable
  


f() # prints 'Simple function'

 

 
 
Functions can also create functions.

 

Functions within functions

 

It’s possible to define function inside a function.

 

Nested Functions

 
A function defined inside another function,

is called a nested function.

 
Below we have defined two functions inside another function.
 


def outer_func():
    
    print("I'm in outer_func ")
    
	# defining a function inside 
	
    def inner_one():
    
		print("\tI'm in inner_one ")
    
	# defining another function inside 
	
    def inner_two():
        
		print("\tI'm in inner_two ") 
    
	# calling first function
	
    inner_one()
	
	# calling second function
	
    inner_two()
    
    print("I'm back in outer_func ")
    
    
outer_func()    
   
    
output:
    
I'm in outer_func
 
	I'm in inner_one 
	
	I'm in inner_two 
	
I'm back in outer_func 

 
 

Notice we have called inner_one() and inner_two()

inside outer_func() block.

can we call inner_one() directly ?
  

inner_one()


output:
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-10-5aae44ee5b62> in <module>()
----> 1 inner_one()

NameError: name 'inner_one' is not defined
 

 
 

We get NameError, because inner_one is not in globals.
 

Local Functions

 

Functions defined inside a function,

are also called local functions,

because they are local to outer function.

Now you understand how a function creates another function.

It’s time to understand the variable scope,

with respect to functions.
 

Variable scopes

 

Variables are evaluated by simple scoping rules.

Usually known as LEGB rules.

LEGB is an abbreviation for

Local, Enclosing, Global Built-in.

Variables are evaluated from inner to outer scopes,

A variable definition lookup starts from local scope,

if it is not found, then look in enclosing scope, and so on.

The order of the look up is

 
Local -> Enclosing -> Global( module scope) -> Built-in

A simple example show the the scoping of variables.

 
Ex 1:
  

gl = 'global_value'

print('Global scope')

def outr_func(arg ='arg_value'):

    print("\tEnclosing scope")
    
    en ='enclosed_value'

    def inner_func():
        lc = 'local_value'
        
        print("\t\t Local Scope")

        print(lc,en,arg,gl)

    print('\tBack to enclosing scope')
    
    inner_func()

  
    #print("can't access local here", lc)

outr_func()


output:

Global scope

	Enclosing scope
	
	Back to enclosing scope
	
		 Local Scope
		 
local_value enclosed_value arg_value global_value

 
 

Did you notice this line of code,
 

 print(lc,en,arg,gl)

 
 

We are printing lc,en,arg,gl,

these are variables are defined in local,

enclosed, and global scope respectively.

‘lc’ is defined inside inner(),

so it can access its variables, its straight forward.

But how inner() function can access en,gl variables?

It is through LEGB rules.

Definition of ‘el’ is looked up first inner() function,

since its not available, move one level up, i.e enclosing scope.

Similrly for ‘arg’,’gl’ definition lookup.

The lookup continues till built-in scopes.

If Python doesn’t find definition at all even in

built-in scope, it raises ‘NameError’ exception.

You can’t access, inner scope variables in outer scope,

for example, if you try to access ‘lc’ in enclosing scope

again the NameError exceptionis raised.

  

#partial code shown

 inner()
    
    print("can't access local here", lc)

outer()


output:

NameError: name 'lc' is not defined

 
 
What is the use of local functions ?

Local function are useful for specialized,

one-off activities.

Local function helps in code organization and readability,

They are like lambda expression, but are more intuitive.

 

Function as an argument to a function.

 
Functions are first class objects, that means,

a function can be passed as an argument to a function

and can be returned as value too.

 
Ex 1:
 

Below we have a simple function,

which is takes a function as argument, and returns a value.

Let’s look at some simple examples.
 
Ex 1:
  

function takes function as an argument and returns it.

def my_len_func(f, x):

    return f(x)
	
	
Calling my_len_func()

my_len_func(len,'justlearnpython')

 
 
We have passed two arguments to my_len_func(),

first is a built-in len() function and

‘justlearnpython’ a string as second argument.

in the code ‘return f(x)’

‘f(x)’ is evaluated to len(‘justlearnpython’)

 

Ex 2:
 
Another example shows a function defined inside another,
  

def sort_by_last_letter(strings):

	def get_last_letter(s):
	
		return s[-1]
		
	return sorted(strings,key=get_last_letter)
	

sort_by_last_letter(['Python','Java','Ruby','Scala','JavaScript'])

output:

['Java', 'Scala', 'Python', 'JavaScript', 'Ruby']

 
 
Here in this example we have an outer sort_by_last_letter() function,

and last_letter() is an inner function, which is used as helper function

to sorted() built-in function.

In above examples we have used built-in functions

inside inner function.

Let’s look at few examples of user defined

outer and inner functions.

 

Ex 3:
 
Another simple example,
  

def enclosing():

	def local_func():
	
		print("Im local func")
		
	return local_func

# lcl_func assigned with enclosing function object.
	
lcl_func = enclosing()


# lcl_func reference is called as function.

lcl_func()

output:

I'm local func

 
 
A local_func() is defined and returned from a enclosing() function.

Notice how the function is returned, ‘return local_func’

the ‘( )’ are not used, local_func is a reference which being returned.

 

Clousres

 
What is a clousre?
 
A closure is just function which is defined inside another

function, which uses values from enclosing scope.

Closure function remembers values of

in enclosing scopes, even if they are not present in memory.

Which is, even after outer function completes execution,

the inner function remembers, references of outer function values.

It’s bit confusing right?, let’s look at an example.

 
Ex 4:
 
Closure example, A function returned from another function.
  

def outer():

    x = 10

    def inner(y):

        return x + y

    return inner

 
 

In above example outer() function returns inner()

function. The inner function takes one argument ‘y’,

it returns sum of ‘x’ and ‘y’,

where ‘x’ a variable in enclosed scope.

Now let’s call inner and outer functions.
  

clousre1 = outer()

a = clousre1(20)

print(a)

output:

30

 
 
When we call outer() function,

  

clousre1 = outer()

 
 
outer() returns reference to inner() function,

which is assigned to clousre1 variable.

Calling inner() function.

inner() function is called with ( ) brackets on clousre1 variable.
  

a = clousre1(20) # y is 20 

'a' has value 30.

 
 

Note inner() function uses value of ‘x’,

inside it’s scope and returns ‘x + y’.

How did inner() function access ‘x’ even though

outer() function already finished its execution.

at clousre1 = outer(),

That’s because, inner() function remembers reference to ‘x’,

and it access ‘x’ with this reference,

when it is called at,
  

a = clousre1(20) # y is 20

 
 
Let’s look at two more simple examples of closure.

 
Ex 4:
 
Inner function uses outer function variable.

This example is same as above, the only difference

here is outer takes an argument ‘n’
  

  
def outer(n):

    def inner(x):

        return n + x

    return inner

clousre1 = outer(10)

clousre2 = outer(100)

a = clousre1(1)

print(a)

b = clousre2(1)

print(b
	
	
output:

11

101

 
 

In this example, outer() function, ‘n’ as argument.

inner() function takes ‘x’ as argument,

it returns the sum of ‘n’ and ‘x’

We have two variables to inner() functions, closure1 and clousre2,

which when called returns 11, 101 values respectively.

Python implements closure with a special method __cloure__

We can examine the closure by accessing this method.
  

clousre1.__closure__


output:

cell at 0x000001BCF0F73408: int object at 0x0000000073EE61C0>

 
 

This shows closure1 referring to a single object,

i’e ‘n’ from the above example.

suppose we have another variable in enclosing scope ‘y’
  

def outer(n):

    y = 20
    
    def inner(x):

        return n + x + y

    return inner

clousre1 = outer(10)

a = clousre1(1)

print(a)


output:

31

 
 

Examine the __closure__ on closure1
  

closure1.__closure__

output:

(<cell at 0x000001BCF0F73E58: int object at 0x0000000073EE61C0>,
 <cell at 0x000001BCF0F73BE8: int object at 0x0000000073EE6300>)

 
 

The output shows closure1 is referring to two objects,

‘n’, ‘y’ from enclosing scope.

 

Factory Functions

 
Closures are commonly used to create factory functions.

Factory functions return specialized function, which does some special

operation on enclosed values.

 
Ex 1:
 
In this example, raise_to_power() is a factory function,

which returns, special function raise_to_y()
  

def raise_to_power(y):
    
    def raise_to_y(x):
        
        return pow(x,y)
    
    return raise_to_y


to_sqare = raise_to_power(2)

to_cube = raise_to_power(3)

to_sqrt = raise_to_power(0.5)

to_fourth = raise_to_power(4)



calling raise_to_y()

to_sqare(2)

output:

4

to_cube(2)

output:

8

to_fourth(2)

output:

16

to_sqrt(2)

output:

1.4142135623730951


 
 

here to_sqare, to_cube, to_fourth, to_sqrt all are closures.

We can cross check this by
  

to_sqare.__closure__

output:

(<cell at 0x000001BCF0F733D8: int object at 0x0000000073EE60C0>,)

 
 

We are almost done with closure, but with concept ‘nonlocal’

 

What is nonlocal ?

  

x = 1

def outer():
    
    x = 10
    
    def inner():
        
        x = 20
        
        print("inner : x = ",x)
        
    print("enclosing_1 : x =",x)
    
    inner()

    print("enclosing_2 : x =",x)
    
print('global_1 : x =',x)

outer()

print('global_2 : x =',x)



output:

global_1 : x = 1

enclosing_1 : x = 10

inner : x =  20

enclosing_2 : x = 10

global_2 : x = 1

 
 

Closely observe the output.

Why value of ‘x’ is did not change in outer() or inner() function.

Its because all variables are local in their scope.

If you want to modify a value of a enclosed or global variable

use ‘nonlocal’ keyword, which is explicitly telling Python

don’t treat it as local in current scope.

Let’s the use of nonlocal
  

def outer():
    
    x = 10
    
    def inner():
        
        nonlocal x 
        x = 20
        
        print("inner : x = ",x)
        
    print("enclosing_1 : x =",x)
    
    inner()
    print("enclosing_2 : x =",x)
    
print('global_1 : x =',x)

outer()

print('global_2 : x =',x)


output:

global_1 : x = 1

enclosing_1 : x = 10

inner : x =  20

enclosing_2 : x = 20

global_2 : x = 1

 
 

Now you see, we have modified our code a bit

in inner() function, we have explicitly said

nonlocal x 

 
 

before assigning a new value to x.
  

x = 20

 
 
So when inner() is called, the value of ‘x’ is

changed from 10 -> 20
  

enclosing_1 : x = 10

inner : x =  20

enclosing_2 : x = 20


 
 

In the next lesson you will, learn how closures can be used as Decorators.