Python lambdas and variable scope
Recently, a colleague posted the following code snippet and asked for an explanation of why it didn't work as they expected:
x = [] for i in range(1, 10): x.append(lambda z: "%d" % i + z) print(list(x[i]("banzaii") for i in range(0, 9)))
['9banzaii', '9banzaii', '9banzaii', '9banzaii', '9banzaii', '9banzaii', '9banzaii', '9banzaii', '9banzaii']
['1banzaii', '2banzaii', '3banzaii', '4banzaii', '5banzaii', '6banzaii', '7banzaii', '8banzaii', '9banzaii']
The key is that the variable i is outside of the scope of the created lambda. Only the variable z is in the lambda scope. Essentially it would be like writing:
x=[] def fn(z): return "%d" % i + z for i in range(1, 10): x.append(fn)
which makes it a lot more obvious what is going on. The global variable i is used each time the function is actually called--which is after the for loop is executed and i is now 9.
The fix is simple: make sure that i is in the scope of the function that is appended to array x.
Solution 1: using a closure
def makefunc(i): def inner(z): return "%d" % i + z return inner x=[] for i in range(1, 10): x.append(makefunc(i))
In this example, a closure around the function inner passes i into the returned function's scope.
Solution 2: using lambda to create an anonymous closure
x=[] for i in range(1, 10): x.append((lambda n: lambda z: "%d" % n + z)(i))
Here, we are appending the result of calling a lambda function with the argument i which returns a lambda that uses that value. This is exactly equivalent to the previous example, but for people who like one-liners. :)
Solution 3: using a lambda with a default argument
x=[] for i in range(1, 10): x.append(lambda z, n=i: "%d" % n + z)
This solution ensures that we are using a variable n that is in the lambda's scope and is initialized by default to i. A possible issue with this solution is if the caller of the function in x[i] passes an additional argument which overrides the default value of n. With the previous solutions, this bug would raise a TypeError exception instead of running successfully with (most likely) unintended results.