RC: W4 D4 — How closures capture variables

March 7, 2024

While I wanted to add tests on my locking mechanisms, I came upon an interesting bug. Here is a simplification of it:

lambda_functions = []
for i in range(5):
    lambda_functions.append(lambda: i)

# Executing the lambda functions
results = [func() for func in lambda_functions]
print(results)

I would have expected this to print [0, 1, 2, 3, 4]. Instead, I got [4, 4, 4, 4, 4]!

What is happening here comes from the way that closures capture variables:

In this case, the lambda functions are executed outside the for loop, when the i variable is set to 4. This is why we get [4, 4, 4, 4, 4] instead of [0, 1, 2, 3, 4].

If we set i = 7 and then re-run print([func() for func in lambda_functions]) then we would get: [7, 7, 7, 7, 7].

In order to resolve the problem, we can create a new scope for each lambda by defining an additional function (create_lambda) that captures the value of the loop variable (i) by passing it as an argument (therefore, each lambda has its own i value):

def create_lambda(x):
    return lambda: x


lambda_functions_corrected = []
for i in range(5):
    lambda_functions_corrected.append(create_lambda(i))

# Executing the corrected lambda functions
corrected_results = [func() for func in lambda_functions_corrected]
print(corrected_results)

Here, the create_lambda function is called at each iteration in the for loop. Thus, each lambda function that is returned has its own scope with a copy of i at the time the lambda was created. Therefore, the results printed are [0, 1, 2, 3, 4]. The issue is solved! Hurray! 🎉

For information, there is another way to handle this: we can capture the value of i at each iteration by using it as a default parameter value for the lambda function. Here is what it looks like:

lambda_functions_corrected = []
for i in range(5):
    lambda_functions_corrected.append(lambda i=i: i)

# Executing the corrected lambda functions
corrected_results = [func() for func in lambda_functions_corrected]
print(corrected_results)

For reference, here is where I found the explanation for it!