Lecture 27-28 Closures and Decorators
Lecture 27-28 Closures and Decorators
fn = outer()
fn = outer()
fn()
Modifying free variables
def counter(): closure count is a free variable
count = 0
it is bound to the cell count
def inc():
nonlocal count count
+= 1 return count
fn = counter()
fn() → 1 count's (indirect) reference changed from the object 0 to the object 1
fn() → 2
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
def inc1():
nonlocal count
count += 1 return count is a free variable – bound to the same count
count
def inc2():
nonlocal count count
+= 1 return count returns a tuple containing both closures
f1, f2 = outer()
f1() → 1
f2() → 2
Shared Extended Scopes
You may think this shared extended scope is highly unusual… but it's not!
def adder(n):
def inner(x):
return x + n
return inner
add_1 = adder(1)
add_2 = adder(2) Three different closures – no shared scopes
add_3 = adder(3)
add_1(10) → 11
add_2(10) → 12
add_3(10) → 13
Shared Extended Scopes
But suppose we tried doing it this way:
adders = []
for n in range(1, 4):
adders.append(lambda x: x + n)
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 the free variable in the
n = 3: lambda is n, and it is bound to the (same) n we created in the loop
adders[0](10) → 13
adders[1](10) → 13
adders[2](10) → 13
Remember, 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 to 3)
Nested Closures
def incrementer(n):
# inner + n is a closure
def inner(start):
current = start
# inc + current + n is a closure
def inc():
nonlocal current
current += n
return current
return inc
return inner
(inner)
fn = incrementer(2)
Nested Closures
def incrementer(n):
# inner + n is a closure
def inner(start):
current = start
# inc + current + n is a closure
def inc():
nonlocal current
current += n
return current
return inc
return inner
(inner)
fn = incrementer(2) → fn.__code__.co_freevars → 'n’ n=2
Nested Closures
def incrementer(n):
# inner + n is a closure
def inner(start):
current = start
# inc + current + n is a closure
def inc():
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)
Nested Closures
def incrementer(n):
# inner + n is a closure
def inner(start):
current = start
# inc + current + n is a closure
def inc():
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
Nested Closures
def incrementer(n):
# inner + n is a closure
def inner(start):
current = start
# inc + current + n is a closure
def inc():
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'
(calls inc) current=100, n=2
inc_2()
Nested Closures
def incrementer(n):
# inner + n is a closure
def inner(start):
current = start
# inc + current + n is a closure
def inc():
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'
(calls inc) current=100, n=2
inc_2() → 102 (current = 102, n=2)
Nested Closures
def incrementer(n):
# inner + n is a closure
def inner(start):
current = start
# inc + current + n is a closure
def inc():
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'
(calls inc) current=100, n=2
inc_2() → 102 (current = 102, n=2) (current
inc_2() → 104 = 104, n=2)
DECORATO
Recall the simple closure example we did which allowed to us to maintain a count of how
RS
many times a function was called:
def counter(fn): count = 0 using *args, **kwargs means we can call any
def inner(*args, **kwargs): function fn with any combination of positional and
nonlocal count keyword-only arguments
count += 1
• print('Function {0} was called {1} times'.format(fn.__name__, count) return fn(*args, **kwargs)
• return inner
We essentially modified our add function by wrapping it inside another function that added some
functionality to it
We also say that we decorated our function add with the function counter
And we call counter a decorator function
Decorators
In general a decorator function:
• takes a function as an argument
• returns a closure
• the closure usually accepts any combination of parameters
• runs some code in the inner function (closure)
• the closure function calls the original function using the arguments passed to the closure
• returns whatever is returned by that function call
closure
inner
inner function
function
(*args,**kwargs)
(*args, **kwargs)
does something…
does something…
returns fn(*args, **kwargs)
returns fn(*args, **kwargs)
Decorators and the @ Symbol
In our previous example, we saw that counter was a decorator
and we could decorate our add function using: add = counter(add)