Closures (Livehacking Screenplay)

def is a Statement

Demonstrate:

  • Functions are objects, just like integers are

  • def used inside another function ⟶ local variable

  • function as return value

# f() -> error

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

f()

g = f
g()
$ python code/05_functions_are_objects.py
f called
f called
  • def is a statement

  • creates function object, and name f in current scope that refers to it

  • g = f demonstrates this

  • Why shouldn’t we return a function from another function

  • Function that creates a function?

def create_f():
    def f():
        print('inner f called')
    return f

inner = create_f()
inner()
$ python code/10_create_function.py
inner f called
  • def called in function scope

  • name f is local

  • returned

Global Scope

  • Variables in global scope can be accessed by “inner” functions (no surprise)

g = 1

def create_f():
    def f():
        print('inner f called, g =', g)
    return f

inner = create_f()
inner()
$ python code/20_global_read.py
inner f called, g = 1
  • global variable g

  • any function in the same global scope (module scope) can access it

  • inner function f can, too, as expected

And Intermediate Scope?

  • Variables in the fnction containing the def?

def create_f():
    intermediate = 1
    def f():
        print('inner f called, intermediate =', intermediate)
    return f

inner = create_f()
inner()
$ python code/30_intermediate_closure.py
inner f called, intermediate = 1
  • create_f’s scope is gone after call returns

  • Inner f has been returned by that call

  • Obviously inner def f(): ... has captured variable intermediate of enclosing scope

  • ⟶ Definition of closure

A Less Theoretical “Use Case”

def create_f():
    intermediate = 1
    def f():
        print('f called, intermediate =', intermediate)
    return f

# inner = create_f()
# inner()

def print_it(msg):
    def _p():
        print(msg)
    return _p

blah = print_it('blah')
something = print_it('something')

blah()
something()
$ python code/35_closure_use_case.py
blah
something
  • (self explanatory)

Assignment to Global Scope

  • Recap of global keyword

  • … used inside inner function

g = 1

def create_f():
    def f():
        g = 2
        print('inner f called, g =', g)
    return f

inner = create_f()
inner()
print('global g =', g)
$ python code/40_global_assignment_wrong.py
inner f called, g = 2
global g = 1
  • Revert closure stuff back to global

  • Assign to global variable, forgetting global

  • Ah yes, f() assigned to (⟶ created) local variable g. This is what the global keyword is there for

g = 1

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

inner = create_f()
inner()
print('global g =', g)
$ python code/50_global_assignment_right.py
inner f called, g = 2
global g = 2
  • Ah yes, global

Assignment to Intermediate Scope

  • 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():
        intermediate = 2
        print('assign: intermediate =', intermediate)
    def check():
        print('check: intermediate =', intermediate)
    return assign, check

assign, check = create()
assign()
check()
$ python code/60_intermediate_assignment_wrong.py
assign: intermediate = 2
check: intermediate = 1
  • rename f into assign

  • variable intermediate instead of global g

  • Discuss: how to check if intermediate has been assigned correctly?

  • ⟶ second local function check

  • Damn! Again created a local variable!

  • nonlocal

def create():
    intermediate = 1
    def assign():
        nonlocal intermediate
        intermediate = 2
        print('assign: intermediate =', intermediate)
    def check():
        print('check: intermediate =', intermediate)
    return assign, check

assign, check = create()
assign()
check()
$ python code/70_intermediate_assignment_right.py
assign: intermediate = 2
check: intermediate = 2
  • Tataa!