0% found this document useful (0 votes)
6 views17 pages

Lecture 27-28 Closures and Decorators

Uploaded by

f2019065117
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views17 pages

Lecture 27-28 Closures and Decorators

Uploaded by

f2019065117
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 17

INTROSPECTION

def outer(): cell 0xA500 str 0xFF100


a = 100
x = 'python' 0xFF100 python
def inner():
a = 10 # local variable
print("{0} rocks!".format(x)) indirect reference
return inner

fn = outer()

fn.__code__.co_freevars → ('x',) (a is not a free variable)

fn.__Closure__ → (<cell at 0xA500: str object at 0xFF100>, )


def outer():
x = 'python'
print(hex(id(x)) 0xFF100 indirect reference
def inner():
print(hex(id(x)) 0xFF100 indirect reference
print("{0} rocks!".format(x)) return inner

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

return inc fn → inc + count → 0

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 counter(): closure f1 = counter() f2 =


counter()
count = 0
def inc():
nonlocal count count f1() → 1
f1 and f2 do not have the same
+= 1 return count f1() → 2 extended scope
f1() → 3 they are different instances of the closure
return inc
f2() → 1 the cells are different
Shared Extended Scopes
def outer():
count is a free variable – bound to count in the extended scope
count = 0

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

return inc1, inc2

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

Now we could call the adders in the following way:

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

• def add(a, b=0): return a + b

•add = counter(add) → Function add was called 1 times


result = add(1, 2) → result = 3

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

outer function (fn)

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)

In general, if func is a decorator function, we decorate another function my_func using:


my_func = func(my_func)

This is so common that Python provides a convenient way of writing that:


@counter @func
def add(a, b): def my_func(…):
return a + b …
is the same as writing is the same as writing

def add(a, b): def my_func(…):


return a + b …
add = counter(add) my_func = func(my_func)

You might also like