Closure

Everything is an Object

  • In other languages, function is just an executable codes, but in Python everything is an object. So function is a type of object.
  • In python, a function is an object, moreover, a function can be an argument in another function or a function can be a result returned from another function
def test():
    pass
a = test
print(type(a))
<class 'function'>

1.1 What is a closure?

In a function, we can return a result which is another function, see below:

def curve_pre():
    def curve():
        pass
# we can't invoke curve() on the global level
curve()
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-21-b9adc0ea2a76> in <module>
      3         pass
      4 # we can't invoke curve() on the global level
----> 5 curve()


NameError: name 'curve' is not defined
def curve_pre():
    def curve():
        print("This is a function")
        pass
    return curve
f = curve_pre()
f()
This is a function
# define a curve function
def curve_pre():
    a = 25
    def curve(x):
        return a * x ** 2
    return curve
f = curve_pre()
print(f(2))
100
a = 10
f = curve_pre()
# note global a will not affect the result, as f will take regional a = 25
print(f(2))
100

Closure = function + environmental variables, so closure will not be affected by the external varialbes.

Closure’s definition has something to do with the environmental variables. If there’s no local variable, then closure will be none; however if there is local varialbe, closure will be defined.

# In this case, because a was not defined within the function, so it will use global variable a
a = 25
# define a curve function
def curve_pre():
    def curve(x):
        return a * x ** 2
    return curve
a = 10
f = curve_pre()
# note global a will not affect the result, as f will take regional a = 25
print(f(2))
print(f.__closure__)
40
None

Closure is stored in __closure__

# define a curve function
def curve_pre():
    a = 25
    def curve(x):
        return a * x ** 2
    return curve
f = curve_pre()
print(f(2))
print(f.__closure__)
print(f.__closure__[0].cell_contents)
100
(<cell at 0x0000022EF0B8D798: int object at 0x000000005F075F30>,)
25

1.2 What’s the meaning of using a closure?

A closure saves both the function and environment variables. Function alone will not guarantee the result will be consistent.

def f1():
    a = 10
    def f2():
        a = 20 # a is an environmental variable that won't affect global var
        print(a)
    print(a)
    f2()
    print(a)
f1()
10
20
10
def f1():
    a = 10
    def f2():
        a = 20 # a is an environmental variable that won't affect global var
        return(a**2)
    return f2
f = f1()
f()
400
print(f)
print("~~~~~~~~~~~~~~~~~")
print(f.__closure__)
<function f1.<locals>.f2 at 0x0000022EF0B67AE8>
~~~~~~~~~~~~~~~~~
None
def f1():
    a = 10
    def f2():
        # remove a assignment, a will have connection with the environment variable
        # If a is a regional var, it will not be a closure
        return(a**2)
    return f2
f = f1()
f()
print(f)
print("~~~~~~~~~~~~~~~~~")
print(f.__closure__)
<function f1.<locals>.f2 at 0x0000022EF0B67D90>
~~~~~~~~~~~~~~~~~
(<cell at 0x0000022EF0B43D98: int object at 0x000000005F075D50>,)

So a cannot be a regional variable that has assignment, it needs to have reference or connection with the environment variables

1.3 An Example with Closure

Closure is relevant with functional programming

origin = 0

def go(step):
    new_pos = origin + step
    origin = new_pos # origin is a local variable and it will not look for it externally
    return origin

print(go(2)) # 2
print(go(3)) # 5
print(go(6)) # 11
---------------------------------------------------------------------------

UnboundLocalError                         Traceback (most recent call last)

<ipython-input-115-f664e25bc942> in <module>
      6     return origin
      7 
----> 8 print(go(2)) # 2
      9 print(go(3)) # 5
     10 print(go(6)) # 11


<ipython-input-115-f664e25bc942> in go(step)
      2 
      3 def go(step):
----> 4     new_pos = origin + step
      5     origin = new_pos # origin is a local variable and it will not look for it externally
      6     return origin


UnboundLocalError: local variable 'origin' referenced before assignment
origin = 0

def go(step):
    new_pos = origin + step
    # origin = new_pos
    return origin

print(go(2)) # 2
print(go(3)) # 5
print(go(6)) # 11
0
0
0

Without using closure, we need to use global variable. Try to avoid changing/accessing global var.

origin = 0

def go(step):
    global origin
    new_pos = origin + step
    origin = new_pos # origin is a local variable and it will not look for it externally
    return origin

print(go(2)) # 2
print(go(3)) # 5
print(go(6)) # 11
2
5
11

With closure, we can use nonlocal to forcibly let python know that an environment variable is not a local var. Closure stores the state of pos after revoking internal function each time.

origin = 0

def factory(pos):
    def go(step):
        nonlocal pos # pos is not a local var
        new_pos = pos + step
        pos = new_pos
        return new_pos
    return go
tourist = factory(origin)
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print(tourist(2)) # 2
print(tourist.__closure__[0].cell_contents)
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print(tourist(3)) # 5
print(tourist.__closure__[0].cell_contents)
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print(tourist(6)) # 11
print(tourist.__closure__[0].cell_contents)
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print(origin) # note that global var origin was not changed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13
13
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16
16
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
22
22
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0