Skip to content

Subhendu

Closures: keep them in your memory

code, python, 🐍1 min read

Recap to inner-function/nested functions in Python

1def factorial(N):
2 if N < 0:
3 raise ValueError("Sorry. We don't do that here...")
4 def compute_factorial(N, accumulated=1):
5 if N<=1:
6 return accumulated
7 return compute_factorial(N-1, N*accumulated)
8 return compute_factorial(N)
9'''
10>>> factorial(10)
113628800
12
13>>> compute_factorial
14Traceback (most recent call last):
15 File "<stdin>", line 1, in <module>
16NameError: name 'compute_factorial' is not defined
17'''

Here we see the inner function compute_factorial being used from the outer-scope of the method factorial. Nothing fancy here, just a bland example of what inner function is. In the above case we can move compute_factorial outside to the same level as factorial and out program would run as expected...

1def compute_factorial(N, accumulated=1):
2 if N<=1:
3 return accumulated
4 return compute_factorial(N-1, N*accumulated)
5def factorial(N):
6 if N < 0:
7 raise ValueError("Sorry. We don't do that here...")
8 return compute_factorial(N)
9'''
10>>> factorial(10)
113628800
12>>> compute_factorial
13<function compute_factorial at 0x10188daf0>
14'''

Although now, compute_factorial is accesible to all other callers who can call factorial. There lies one benifit of inner function: Encapsulation

Inner Function Encapsulation + Memory of the outer scope

Dialing things up a notch is the property of inner functions which have access to outer functions scope and thus its variables.

1def echo(text, to_upper=False):
2 def inner_echo():
3 return "Hah take this: " + ( text.upper() if to_upper else text)
4 return inner_echo()
5'''
6>>> echo("hello", True)
7'Hah take this: HELLO'
8'''

As you can see from the output, inner function have access to variables defined in the scope of the outer function.

Try and guess the execution of the following methods: ( 😉 to a future blog post )

1def test_dict():
2 memory = {}
3 def inner_test():
4 for i in range(10):
5 memory[i] = i*i
6 inner_test()
7 return memory
8
9def test_int():
10 number = 100
11 def inner_test():
12 for i in range(number):
13 number -= i
14 inner_test()
15 return number

Inner functions and Closures in Python

In python functions are first class objects. Few properties of first class functions:

  • Function will be a instance of the Object type.
  • Function can be stored in a variable.
  • Function can be passed to other functions as parameters or can be returned from other functions.

Closure is a function object that remembers values in enclosing scopes even if they are not present in memory any longer. Let's look at an example to understand better:

1def exponent(number):
2 def inner_exponent(value):
3 return number ** value
4 return inner_exponent
5'''
6>>> two_exponent = exponent(2)
7>>> two_exponent(10)
81024
9>>> two_exponent
10<function exponent.<locals>.inner_exponent at 0x1018a61f0>
11'''

In the above example exponent is being used as a factory method which takes number as an argument and returns a new method inner_exponent which is a function object i.e. which uses the data passed in the outer function for its own execution much later after the outer function has been executed.

To put it another way, the closure "initializes" the number in inner_exponent() and then returns it. Now, whenever you call that newly returned function, it will always see its own private snapshot that includes number.

Conclusion 🐍 🐍

Closures can be really powerful when you want to encapsulate information/logic from the caller via inner functions.