If it won't be simple, it simply won't be. [Hire me, source code] by Miki Tebeka, CEO, 353Solutions

Tuesday, March 27, 2012

A lambda Gotcha

Quick, what is the output of the following?

In [1]: callbacks = [lambda: i for i in range(10)]
In [2]: [c() for c in callbacks]

The right answer is:
Out[2]: [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]

This is due to the fact that i is bound to the same variable in all the lambdas, and has the final value of 9.

There are two ways to overcome this. The first is to use the fact the default arguments are evaluated at function creation time (which is another known gotcha).

In [3]: callbacks = [lambda i=i: i for i in range(10)]
In [4]: [c() for c in callbacks]
Out[4]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

The second is to create a function generator function:
In [5]: def make_callback(i):
   ...:     return lambda: i
   ...:
In [6]: callbacks = [make_callback(i) for i in range(10)]
In [7]: [c() for c in callbacks]
Out[7]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

2 comments:

KevinX said...

Very interesting question! For pedagogy purposes, I'd rewrite the expression for clarity:

[(lambda x=default: x) for default in range(10)]

This shows 1) separation of lambda generation and the for-loop and 2) shows a local x being set a default value inside the lambda expression. Interesting, thanks for pointing out. Hopefully one doesn't see this in a production code :)

Miki Tebeka said...

Thanks for the input. I agree the lambda i=i is not a good syntax. However I don't feel "default" is a good name as well. Will sleep on it.

Blog Archive