I don't understand the behaviour of a piece of code, involving a comprehension list over lambda functions that call a method in different objects. It happened in a large program, so for this question I made a nonsensical toy-case to show my point:
class Evaluator(object):
def __init__(self, lft, rgt):
self.lft = lft
self.rgt = rgt
def eval(self, x):
return self.lft + x * (self.rgt - self.lft)
if __name__ == "__main__":
ev1 = Evaluator(2, 3)
ev2 = Evaluator(4, 5)
ev3 = Evaluator(6, 7)
funcs = [lambda x:ev.eval(x+0.1) for ev in (ev1, ev2, ev3)]
print([f(0.5) for f in funcs])
The output I get is [6.6, 6.6, 6.6]
, which means that it is the method in ev3
the one that is being evaluated all the time. Instead of [2.6, 4.6, 6.6]
, as I would have expected. But what really surprises me is that if I get rid of the lambda-function, the behaviour is fine:
class Evaluator(object):
def __init__(self, lft, rgt):
self.lft = lft
self.rgt = rgt
def eval(self, x):
return self.lft + x * (self.rgt - self.lft)
if __name__ == "__main__":
ev1 = Evaluator(2, 3)
ev2 = Evaluator(4, 5)
ev3 = Evaluator(6, 7)
funcs = [ev.eval for ev in (ev1, ev2, ev3)]
print([f(0.5) for f in funcs])
returns [2.5, 4.5, 6.5]
. Can anyone explain what is going on here? And how should I code this in a Pythoninstic way?
The problem is that you are only ever evaluating ev at the time you call the function. Thus, it's using whatever value ev
has only when you start the print statement. Of course, by that time ev
has the value of the last function in the list.
This is no different than if you had done this:
funcs = [lambda x: ev.eval(x+0.1),
lambda x: ev.eval(x+0.1),
lambda x: ev.eval(x+0.1)]
Notice how they all use ev
, and they will all use the same ev
at the time you run the functions.
To do what you want, you need to bind ev
to its current value in the list comprehension at the time you define the comprehension, which you can do by passing in the value through the lambda arguments:
funcs = [lambda x, ev=ev: ev.eval(x+0.1) for ev in (ev1, ev2, ev3)]
However, I strongly suggest you do not do this. As you have just experienced, such code is very hard to understand and debug. You win no points for cramming as much functionality into a single line as possible.
The technical term for this is a closure. For more information, check out this question on stackoverflow: Why aren't python nested functions called closures?