데먕 2019. 11. 7. 17:09

1. Overview

Closure can be consist of a function plus an extended scope that contains the free variables. 

2. Description

2.1 Free Variable and Inner Function

Functions defined inside another function can access the outer (nonlocal) variables

def outer():
    x = [1, 2, 3]
    print('outer:', hex(id(x)))
    def inner():
        print('inner:', hex(id(x)))
        print(x)
    return inner
    
    
fn = outer()
fn()

x is a free variable in inner. It is bound to the variable x in outer this happens when outer runs that mean inner function is created. This the closure. When we return inner, we are actually "returning" the closure.

We can assign that return value to a variable fn. When we called fn at that time python determined the value of x in the extended scope. But notice that outer had finished running before we called fn. Its scope was gone.

2.2 Python Cells and Multi-scoped variables

def outer():
    x = 'python'
    def inner():
        print(x)
    return inner

Here the value of x is shared between two scopes

  • outer
  • closure

The label x is in two different scopes but always reference the same value. Python does this by creating a cell as an intermediary object. In effect, both variables x (in outer and inner), point to the same cell. When requesting the value of the variable, Python will "double-hop" to get to the final value

2.3 Closure

You can think of the closure as a function plus an extended scope that contains the free variables. The free variable's value is the object the cell points to so that could change over time. Every time the function in the closure is called and the free variable is referenced. Python looks up the cell object, and then whatever the cell is pointing to 

2.4 Multiple Instances of Closures

Every time we run a function, a new scope is created. If that function generates a closure, a new closure is created every time as well.

2.5 Shared extended scopes

adders = []
for n in range(1,4):
    adders.append(lambda x: x + n)
    
adders[0](10)
# 13
adders[1](10)
# 13
adders[2](10)
# 13

n = 1: the free variable in the lambda is n, and it is bound to the "n" we created in the loop

n = 2: the free variable in the lambda is n, and it is bound to the (same) n we created in the loop

n = 3: the free variable in the lambda is n, and it is bound to the (same) n we created in the loop

Python does not evaluate the free variable n until the adders[i] function is called. Since all three functions in adders are bound to the same n, by the time we call adders[0], the value of n is 3(the last iteration of the loop set n of 3)

3. Example

3.1 Introspection 1

3.2 Introspection 2

3.3 Nested Closures

def incrementer(n):
	# inner + n is a closure
    def inner(start):
        current = start
        # inc + current + n is a closure
        def inc():
            a = 10  # local var
            nonlocal current
            current += n
            return current
            
        return inc
    return inner
    
# inner
fn = incrementer(2)

fn.__code__.co_freevars
# 'n', n = 2

# inc
inc_2 = fn(100)

inc_2.__code__.co_freevars
# 'current, 'n', current = 100, n = 2

# call inc
inc_2()
# 102, current = 102, n = 2


inc_2()
# 104, current = 104, n = 2

4. References

https://mathbyteacademy.com

https://en.wikipedia.org/wiki/Closure_(computer_programming)