Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python create function in a loop capturing the loop variable

Tags:

python

lambda

What's going on here? I'm trying to create a list of functions:

def f(a,b):
    return a*b

funcs = []

for i in range(0,10):
    funcs.append(lambda x:f(i,x))

This isn't doing what I expect. I would expect the list to act like this:

funcs[3](3) = 9
funcs[0](5) = 0

But all the functions in the list seem to be identical, and be setting the fixed value to be 9:

funcs[3](3) = 27
funcs[3](1) = 9

funcs[2](6) = 54

Any ideas?

like image 304
Dan Lorenc Avatar asked Jul 10 '09 01:07

Dan Lorenc


People also ask

Can you put a function in a loop Python?

Python call function inside for loop A Python for loop is used to loop through an iterable object (like a list, tuple, set, etc.) and perform the same action for each entry. We can call a function from inside of for loop.

Can you put a function inside a loop?

Accepted Answer "function" as a keyword is only used for defining functions, and cannot be used inside a loop (or inside an "if" or "switch" or other control statement.) The only kinds of functions that can be defined within loops are anonymous functions.

Can I call a function inside a while loop?

Answer. Yes, you can use a function call in the while expression. If calling only a function in the expression, it should return True or False . If the function is part of a more complex expression, then the end result of the expression should evaluate to True or False .

Can a for loop be a variable in Python?

Python for loop components In general, any object that supports Python's iterator protocol can be used in a for loop. A variable that holds each element from the container/sequence/generator.


3 Answers

lambdas in python are closures.... the arguments you give it aren't going to be evaluated until the lambda is evaluated. At that time, i=9 regardless, because your iteration is finished.

The behavior you're looking for can be achieved with functools.partial

import functools

def f(a,b):
    return a*b

funcs = []

for i in range(0,10):
    funcs.append(functools.partial(f,i))
like image 180
Ryan Avatar answered Oct 05 '22 22:10

Ryan


Yep, the usual "scoping problem" (actually a binding-later-than-you want problem, but it's often called by that name). You've already gotten the two best (because simplest) answers -- the "fake default" i=i solution, and functools.partial, so I'm only giving the third one of the classic three, the "factory lambda":

for i in range(0,10):
    funcs.append((lambda i: lambda x: f(i, x))(i))

Personally I'd go with i=i if there's no risk of the functions in funcs being accidentally called with 2 parameters instead of just 1, but the factory function approach is worth considering when you need something a little bit richer than just pre-binding one arg.

like image 26
Alex Martelli Avatar answered Oct 06 '22 00:10

Alex Martelli


There's only one i which is bound to each lambda, contrary to what you think. This is a common mistake.

One way to get what you want is:

for i in range(0,10):
    funcs.append(lambda x, i=i: f(i, x))

Now you're creating a default parameter i in each lambda closure and binding to it the current value of the looping variable i.

like image 34
ars Avatar answered Oct 06 '22 00:10

ars