Lambda in For Loop Only Takes Last Value

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



Leave a reply



Submit