for
loop and lazy evaluationwritten by @marc_lelarge
used to check python prerequisites of the deep learning course dataflowr. Before reading this post, you should start with the quiz
If you are not familiar with binding (i.e. assignment) and mutation of variables in Python you can have a look at Binding vs Mutation.
Before looking at the leaking problem, we illustrate basic properties of scope in python:
def print_11():
x = 11 # new local variable inside the scope of print_11
print(x) # will print the local variable x = 11
def print_x_outside():
print(x) # will print the global variable x (not defined yet)
x = 10 # global variable x = 10
def main():
def print_x_inside():
print(x) # will print the variable x in the scope of main (not defined yet)
x = 11 # variable x = 11 in the scope of main
print_11() # print 11
print_x_inside() # print 11
x = 12 # new binding for the variable x
print_x_outside() # print 10
print_11() # print 11
print_x_inside() # print 12
main()
Assume, we want to print the old value of x
and then modify it. The following code will produce an error:
def main():
def print_modify(new_value):
print(x)
x = new_value
x = 10
print_modify(11)
main()
The last statement in print_modify
assigns a new value to x
, the compiler recognizes it as a local variable. Consequently when the earlier print(x)
attempts to print the uninitialized local variable and an error results, see Why am I getting an UnboundLocalError when the variable has a value?
Probably the easiest way to do this:
def main():
def easy_print_modify(x,new_value):
print(x)
return new_value
x = 10
x = easy_print_modify(x,11)
print(x)
main()
or you can use nonlocal
:
def main():
def other_print_modify(new_value):
nonlocal x
print(x)
x = new_value
x = 10
other_print_modify(11)
print(x)
main()
def print_12():
x = [1, 2]
print(x)
def print_x_outside():
x[0] = 3
print(x)
x = ['a', 'b']
def main():
def print_x_inside():
x[1] = 'b'
print(x)
x = [1, 2]
print_x_inside() # print [1, 'b']
print(x) # print [1, 'b']
print_12() # print [1, 2]
x = [1,2,3]
print_x_outside() # print [3, 'b']
print_x_inside() # print [1, 'b', 3]
print(x) # print [1, 'b', 3]
print_12() # print [1, 2]
main()
print(x) # print [3, 'b']
for
loopbasic example: even the variable _
becomes a variable accessible outside the for
loop!
for _ in range(10):
foo = 2
print(_, foo) # print 9 2
confusing variables:
i = 20
list_a = []
for i in range(10):
list_a.append(2*i)
print(i) # print 9 - leaking -
i = 20
litst_b = [2*i for i in range(10)]
print(i) # print 20 - no leaking -
Takeaways: List comprehensions have their own scope, loops don't.
Variables in functions are evaluated only when called:
list_fun = []
for i in range(10):
list_fun.append(lambda: i)
for f in list_fun:
print(f())
# print 9 ten times
print(i) # print 9
i = 20
for f in list_fun:
print(f())
# print 20 ten times
Here i
is not local to the lambdas but is defined in the outer scope, and it is accessed when the lambda is called — not when it is defined. At the end of the loop, the value of i
is 9
, so all the functions now return 9
.
See also Why do lambdas defined in a loop with different values all return the same result?
With list comprehension, there is a local scope (as seen above) so now i
is local but since the function is not called at creation, the local variable i
is not evaluated only when it is called but then i=9
.
list_fun_comp = [lambda: i for i in range(10)] # variable i is in the local scope
for f in list_fun_comp:
print(f())
# print 9 ten times
i = 20 # this variable is in the global scope
for f in list_fun_comp:
print(f())
# print 9 ten times
We now present several ways to solve the problem:
Using an argument with a default value thanks to i=i
below, will creates a new variable i
local to the lambda and computed when the lambda is defined:
list_fun_hack = [lambda i=i: i for i in range(10)]
for f in list_fun_hack:
print(f())
# print 0 1 2 3 4 5 6 7 8 9
In mathematics and computer science, currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each takes a single argument. Here this is very simple since i
in the for
loop, the local variable i
is evaluated:
list_fun_curry = [(lambda x :(lambda: x))(i) for i in range(10)]
for f in list_fun_curry:
print(f())
# print 0 1 2 3 4 5 6 7 8 9
Variables used in the generator expression are evaluated lazily see Generator expressions so you can use a generator:
def fun_gen(n):
i = 0
while i < n:
yield (lambda: i)
i += 1
for f in fun_gen(10):
print(f())
# print 0 1 2 3 4 5 6 7 8 9
But this code will not work as intended:
for f in list(fun_gen(10)):
print(f())
# print 10 ten times
Python Oddities Explained by Trey Hunner
Common Gotchas from The Hitchhiker’s Guide to Python!
Back to the deep learning course dataflowr
public
python
dataflowr