13

I want to write a function which returns a list of functions. As a MWE, here's my attempt at function that gives three functions that add 0, 1, and 2 to an input number:

def foo():
    result = []
    for i in range(3):
        temp = lambda x: x + i
        print(temp(42))  # prints 42, 43, 44
        result.append(temp)
    return result

for f in foo():
    print(f(42))  #prints 44, 44, 44

Contrary to my expectation, each function ends up using the last value taken by i. I've experienced similar behavior when e.g. a list is used as argument to a function but Python actually uses a pointer to the list, but here i is an integer, so I don't understand what's going on here. I'm running Python 3.5.

bjarkemoensted
  • 2,557
  • 3
  • 24
  • 37

1 Answers1

15

Closures bind late, meaning they will use the last value of i in the scope they have been defined when called (in this case the last value of i will be 2).

The common solution to this is providing the value of i as a default argument:

temp = lambda x, i=i: x + i

Since Python evaluates default arguments during the construction of a function, this is a trick that helps Python use the correct value of i when the callable is invoked, it doesn't create a closure and, instead, can look i up in it's locals.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
  • 2
    More info about this answer http://quickinsights.io/python/python-closures-and-late-binding/ – Mauro Baraldi Feb 02 '17 at 13:30
  • Thanks! Just to make 100% sure I understand - by this you mean that the value used for all i's is the value of i when the code exits the scope of the function definition? – bjarkemoensted Feb 02 '17 at 13:32
  • 1
    I prefer using `functools.partial` instead of assigning default arguments, seems cleaner. – Georg Schölly Feb 02 '17 at 13:36
  • 1
    @ahura pretty much, yes. `i`, when looked up during the loop execution (with your `print` calls), will correctly grab the current value of `i` as it is incrementing. When the function ends and you return your result, the local value of `i` will be `2` and that's the value the `lambda`s will use when invoked outside of the function. – Dimitris Fasarakis Hilliard Feb 02 '17 at 13:43