4

I am trying to create a list of functions that I then apply to a list of numbers. I do this by iterating through a range of numbers, and defining a lambdas function with each number from the for loop. Then a second for loop is used to apply each function to the same list of 10 numbers.

The problem is that my list of lambdas seem to all take the final value of the for loop.

fx_lst = []
for k in range(1,3,1):
    func = lambda x_: k * x_
    fx_lst.append(func)
xlst = range(1,10,1)

for fx in fx_lst:
    ylst = map(lambda xin_: fx(xin_), xlst)
    print i, ylst

ylst prints: [2,4,6...18] [2,4,6...18]

Obviously I'm not understanding something about the way lambdas store variable information.

cs95
  • 379,657
  • 97
  • 704
  • 746
saeranv
  • 155
  • 1
  • 7

2 Answers2

3

What's happening is that your lambdas are being bound to the loop variable k rather than the value associated with it, that's all it is. The loop variable k persists even after the loop, and so those lambdas have access to it.

One possible solution I know is lambda currying:

In [129]: fx_lst = []
     ...: for k in range(1,3,1):
     ...:     func = (lambda k=k: lambda x_: k * x_)(k)
     ...:     fx_lst.append(func)
     ...:     

In [130]: fx_lst[0](3)
Out[130]: 3

In [131]: fx_lst[1](3)
Out[131]: 6

The function (lambda k=k: lambda x_: k * x_)(k) is basically a higher order function that reduces a function taking two parameters to a function taking one, by fixing one of the parameters constant in advance. The scope is fixed with k=k so the outer loop variable does not conflict with the scope of the lambda.

For your example, I get

[1, 2, 3, 4, 5, 6, 7, 8, 9]
[2, 4, 6, 8, 10, 12, 14, 16, 18]

The other workaround involves using functools.partial instead, which does the exact same thing.

cs95
  • 379,657
  • 97
  • 704
  • 746
  • 1
    Well explained, and works perfectly. Thank you. I've never heard of currying before so that's something I need to research! – saeranv Sep 05 '17 at 01:50
  • 2
    @user2185097 Glad I could help! You may be interested in https://stackoverflow.com/questions/36314/what-is-currying – cs95 Sep 05 '17 at 01:51
  • You can use default parameter `k=k` directly in OP's lambda definition for immediate binding and arguably simpler code – Mad Physicist Apr 15 '21 at 11:08
1

I run into this issue before, and the following is a solution:

def apply_filters(filters, iterator):
    from functools import reduce
    return list(reduce(lambda s, f: filter(f, s), filters, iterator))

Usage:

my_filters = [lambda x: x % 2 == 0, lambda x: x % 5 == 0]
result = apply_filters(my_filters, range(1, 100))
print(result)
# [10, 20, 30, 40, 50, 60, 70, 80, 90]
Menglong Li
  • 2,177
  • 14
  • 19