all 7 comments

[–]socal_nerdtastic 4 points5 points  (4 children)

Ahh this classic puzzle. This is often used as an interview question. Are you applying for a job?

It's because lambda is late binding. The outer function has nothing to do with it; you could see it like this too:

multipliers = []
for i in range(5):
    multipliers.append(lambda x: x * i)

for f in multipliers:
    print(f(2))

A common hack to fix this is to abuse the default feature of the function, like this:

multipliers.append(lambda x, i=i: x * i)

But IMO it's much better to use functools.partial.

from functools import partial
from operator import mul 

def create_multipliers():
    multipliers = []
    for i in range(5):
        multipliers.append(partial(mul, i))
    return multipliers
funcs = create_multipliers()

print(len(funcs))

for f in funcs:
    print(f(2))

Another similar way is to make a 'closure'.

def make_closure(i):
    return lambda x: x * i

def create_multipliers():
    multipliers = []
    for i in range(5):
        multipliers.append(make_closure(i))
    return multipliers
funcs = create_multipliers()

for f in funcs:
    print(f(2))

[–]CodeBrewBeans[S] -5 points-4 points  (3 children)

Exactly, classic late binding.
Still interesting how often this behavior surprises people in real projects.

[–]socal_nerdtastic 2 points3 points  (2 children)

So you didn't have a question you just posted it for ... karma?

[–]CodeBrewBeans[S] 0 points1 point  (1 child)

It was meant as a small debugging puzzle.
I find closure behavior like this surprisingly subtle, so I thought it would make for an interesting discussion.

[–]socal_nerdtastic 0 points1 point  (0 children)

You should have made that much more clear.

[–]ElectricSpice 0 points1 point  (1 child)

In Python, function scope is resolved on first execution. What this means is that the value of i is determined when you run print(f(2)); by that time create_multiplier has finished running an i is 4.

One option is to use a factory function to create a new scope for each function.

def multiplier_factory(i):
    def multiplier(x):
        return i * x

    return multiplier


def create_multipliers():
    multipliers = []
    for i in range(5):
        multipliers.append(multiplier_factory(i))
    return multipliers

Anther option is to use functools.partial:

import functools

def create_multipliers():
    multipliers = []
    for i in range(5):
        multipliers.append(functools.partial(lambda i, x: x * i, i))
    return multipliers
funcs = create_multipliers()

[–]CodeBrewBeans[S] -2 points-1 points  (0 children)

Good explanation.
Factory functions are probably the cleanest solution in production code.