Closures

History

  • Wikipedia

  • “A record storing a function together with an environment”

    • That is pretty much it!

  • Dates back to early functional languages

    • LISP

    • Scheme

Today

  • Also available in modern languages

    • Javascript

    • C++: explicit capturing in lambdas

    • C#

  • Definitely not unique to Python

    • Although Wikipedia starts out with Python examples

def is a Statement

  • def create a variable (the function’s name) that references a function object

  • ⟶ Functions are objects, just like integers are

def f():
    pass

type(f)
function
  • Variable assignment (of function objects)

def f():
    print('f called')

g = f          # <--- assign one function to another
g()            # <--- calls f (as g)
f called
  • def can be used anywhere - e.g. inside another function

  • ⟶ local function definition - local variable

  • ⟶ Functions can create and return other functions

Functions That Create Functions

  • A function that creates a function

def create_f():
    def f():
        print('inner f called')
    return f
  • Create a function

inner = create_f()
  • inner is a variable that references a function object

  • Call it:

inner()
inner f called

Inner Function Reaches Out To Global Scope

  • Variables in global scope can be accessed by “inner” functions

  • No surprise: global variables can be accessed by any function

  • No surprise: global variables have program lifetime

glob = 666

def create_f():
    def f():
        print('inner f called, glob is', glob)
    return f
  • Calling the returned function reveals value of glob

inner = create_f()
inner()
inner f called, glob is 666

And Intermediate Scope? ⟶ Closure

  • Semi global - variables in the creating scope

  • Those do not outlive the creating function

  • Closure

def create_f():
    intermediate = 1                # <--- captured in closure of f
    def f():
        print('inner f called, intermediate =',
              intermediate)         # <--- used *after* f has been returned
    return f

inner = create_f()
  • create_f() is over

  • intermediate is long out of scope

  • But is still alive in the closure

inner()
inner f called, intermediate = 1

A Less Theoretical “Use Case”

def create_print(msg):   # <--- parameters are local variables to the callee
    def p():
        print(msg)       # <--- local variable captured
    return p

print_blah = create_print('blah')
print_something = create_print('something')

print_blah()
print_something()
blah
something

Scope Issues: Assignment to Global Scope (global Keyword)

  • First assignment creates variable in local scope

  • The following is wrong! (At least if you want to assign to global g)

g = 1

def create_f():
    def f():
        g = 2            # <--- *local* variable created
        print('inner f called, g =', g)
    return f

inner = create_f()
inner()
print('global g =', g)
inner f called, g = 2
global g = 1
  • ⟶ Global g still 1

  • Fix: global keyword

g = 1

def create_f():
    def f():
        global g         # <--- every mention of g means the *global* g
        g = 2
        print('inner f called, g =', g)
    return f

inner = create_f()
inner()
print('global g =', g)
inner f called, g = 2
global g = 2

Scope Issues: Assignment to Intermediate Scope (nonlocal Keyword)

  • And now, what about assignment to intermediate scope? To a variable in the closure?

  • Who does this?

  • Many non-obvious use cases, used to improve job security

def create():
    intermediate = 1
    def assign():
        nonlocal intermediate           # <--- that is the point! nonlocal!
        intermediate = 2
        print('assign: intermediate =', intermediate)
    def check():
        print('check: intermediate =', intermediate)
    return assign, check

assign, check = create()
assign()
check()
assign: intermediate = 2
check: intermediate = 2