lambda in for loop only takes last value
Please read about minimal examples. Without reading your code, I believe you have run into a well known issue addressed in previous questions and answers that needs 2 lines to illustrate. Names in function bodies are evaluated when the function is executed.
funcs = [lambda: i for i in range(3)]
for f in funcs: print(f())
prints '2' 3 times because the 3 functions are identical and the 'i' in each is not evaluated until the call, when i == 2. However,
funcs = [lambda i=i:i for i in range(3)]
for f in funcs: print(f())
makes three different functions, each with a different captured value, so 0, 1, and 2 are printed. In your statement
__cMenu.add_command(label="{}".format(option),
command=lambda: self.filter_records(column, option))
add option=option
before :
to capture the different values of option
. You might want to rewrite as
lambda opt=option: self.filter_records(column, opt)
to differentiate the loop variable from the function parameter. If column
changed within the loop, it would need the same treatment.
Lambda in loop stored in list, prints only the last loops evaluated value not different values from all loop iterations
Lambdas are evaluated when they are executed.
They are executed when you print them. The only known foo
at that time is the one from the last loop. So TestObj(1)
is printed twice.
You can verify this by changing your lambda to:
lambdas.append(lambda: id(foo)) # will print the id() of the used foo
You need to "early" bind the foo
from the loop to the lambda:
lambdas.append(lambda x = foo: x.value > 5) # bind x to foo from loop, check x.value
Full fix:
class TestObj():
def __init__(self, value):
self.value = value
lambdas = []
values_list = [10, 1]
for ele in values_list:
foo = TestObj(ele)
# use an x and bind it to this loops foo
lambdas.append(lambda x = foo: x.value > 5)
# this is bad btw, use a simple for loop - do not use a list comp to create a list
# simply to print values
print([single_lambda() for single_lambda in lambdas])
Output:
[True, False]
# and for the changed lambda with id() : the same id() twice
See Is it Pythonic to use list comprehensions for just side effects? regarding using list comprehension sideeffects and why its bad.
Related:
- Unexpected behaviour with a conditional generator expression
Python lambda doesn't remember argument in for loop
The body of the lambda
in your code references the name x
. The value associated with that name is changed on the next iteration of the loop, so when the lambda is called and it resolves the name it obtains the new value.
To achieve the result you expected, bind the value of x
in the loop to a parameter of the lambda
and then reference that parameter, as shown below:
def main():
d = {}
for x in [1,2]:
d[x] = lambda x=x: print(x)
d[1]()
d[2]()
if __name__ == '__main__':
main()
>>>
1
2
Lambda in a loop
You need to bind d for each function created. One way to do that is to pass it as a parameter with a default value:
lambda d=d: self.root.change_directory(d)
Now the d inside the function uses the parameter, even though it has the same name, and the default value for that is evaluated when the function is created. To help you see this:
lambda bound_d=d: self.root.change_directory(bound_d)
Remember how default values work, such as for mutable objects like lists and dicts, because you are binding an object.
This idiom of parameters with default values is common enough, but may fail if you introspect function parameters and determine what to do based on their presence. You can avoid the parameter with another closure:
(lambda d=d: lambda: self.root.change_directory(d))()
# or
(lambda d: lambda: self.root.change_directory(d))(d)
Python lambda function is not being called correctly from within a for loop
This is a classic case of unwanted closure. Here's a simplified example:
funcs = []
for i in range(3):
funcs.append(lambda: i + 1)
for func in funcs:
print(func())
One might expect that this will print 1 2 3
, but it prints 3 3 3
instead. The reason is, lambda
is a closure, closing over the variable i
(capturing it in its context). When we execute the functions, the variable i
is left at its last value in the loop (2
). To reiterate, the lambda
does not capture the value, but the variable. To avoid this, we want to pass the current value of i
into the function as a parameter. To do that, we can construct a function that accepts i
as the parameter, captures the parameter into the closure and returns the "customised" function we want:
from functools import partial
funcs = []
for i in range(3):
funcs.append((lambda val: lambda: val + 1)(i))
for func in funcs:
print(func())
Equivalently, we can use functools.partial
, which does just this:
from functools import partial
funcs = []
for i in range(3):
funcs.append(partial(lambda val: val + 1, i))
for func in funcs:
print(func())
Here, lambda val: val + 1
will expect a parameter; partial(lambda val: val + 1, 0)
will produce a new function where the first parameter is fixed at 0
- basically, lambda: 0 + 1
. This captures no variables, and thus avoids the problem you encountered.
tl;dr:
command=partial(lambda i: self.process(charOrder[i]), imgIndex)
For loop is taking only the last element in the list
Here:
for region in regions:
csvio = io.BytesIO()
writer = csv.writer(csvio)
# ...
s3.put_object(Body=csvio.getvalue(), ContentType='application/vnd.ms-excel', Bucket='unused-sg', Key='Unused_Security.csv', ACL='public-read')
you are creating a new csv file for each region, overwriting the previous one. You want to keep this out of the loop:
csvio = io.BytesIO()
writer = csv.writer(csvio)
for region in regions:
# massage the data
# write to the csv
# ...
# now we're outside the loop we write the full thing
s3 = boto3.client('s3')
s3.put_object(Body=csvio.getvalue(), ContentType='application/vnd.ms-excel', Bucket='unused-sg', Key='Unused_Security.csv', ACL='public-read')
csvio.close()
loop for inside lambda
Since a for
loop is a statement (as is print
, in Python 2.x), you cannot include it in a lambda expression. Instead, you need to use the write
method on sys.stdout
along with the join
method.
x = lambda x: sys.stdout.write("\n".join(x) + "\n")
Related Topics
Count the Number of Occurrences of a Character in a String
Which Python Memory Profiler Is Recommended
How to Explicitly Free Memory in Python
What Is the Eafp Principle in Python
How to Print a Number Using Commas as Thousands Separators
"Inconsistent Use of Tabs and Spaces in Indentation"
How to Sort a List of Objects Based on an Attribute of the Objects
All Combinations of a List of Lists
Loop "Forgets" to Remove Some Items
Understanding the "Is" Operator
How to Define a Two-Dimensional Array
Changing the "Tick Frequency" on X or Y Axis in Matplotlib
How to Call a Script from Another Script
Most Efficient Way to Map Function Over Numpy Array