I'm using Python 3.6.10 in a CentOS 7 environment. I am attempting to create a list of commands to execute based on a structured specification. It seems natural and pythonic to think of this as a list of lambdas. I build the lambda list by traversing the specification. To my surprise, when I execute the result, I find that every lambda is the same because it doesn't capture its argument at the time the lambda is created. I think that's a bug.
Here is sample code that illustrates the behavior:
specification = {
'labelOne': ['labelOne.one', 'labelOne.two', 'labelOne.three', 'labelOne.four', 'labelOne.five'],
'labelTwo': ['labelTwo.one', 'labelTwo.two', 'labelTwo.three', 'labelTwo.four', 'labelTwo.five'],
'labelThree': ['labelThree.one', 'labelThree.two', 'labelThree.three', 'labelThree.four', 'labelThree.five'],
'labelFour': ['labelFour.one', 'labelFour.two', 'labelFour.three', 'labelFour.four', 'labelFour.five'],
'labelFive': ['labelFive.one', 'labelFive.two', 'labelFive.three', 'labelFive.four', 'labelFive.five'],
}
lambdas = []
for label, labelStrings in specification.items():
for labelString in labelStrings:
lambdaString = f"""Label: \"{label}\" with labelString: \"{labelString}\""""
oneArgLambda = lambda someArg: print(someArg, lambdaString)
lambdas.append(oneArgLambda)
for each in lambdas:
each('Show: ')
I expected to see this:
Show: Label: "labelOne" with labelString: "labelOne.one"
Show: Label: "labelOne" with labelString: "labelOne.two"
Show: Label: "labelOne" with labelString: "labelOne.three"
Show: Label: "labelOne" with labelString: "labelOne.four"
Show: Label: "labelOne" with labelString: "labelOne.five"
Show: Label: "labelTwo" with labelString: "labelTwo.one"
Show: Label: "labelTwo" with labelString: "labelTwo.two"
Show: Label: "labelTwo" with labelString: "labelTwo.three"
Show: Label: "labelTwo" with labelString: "labelTwo.four"
Show: Label: "labelTwo" with labelString: "labelTwo.five"
Show: Label: "labelThree" with labelString: "labelThree.one"
Show: Label: "labelThree" with labelString: "labelThree.two"
Show: Label: "labelThree" with labelString: "labelThree.three"
Show: Label: "labelThree" with labelString: "labelThree.four"
Show: Label: "labelThree" with labelString: "labelThree.five"
Show: Label: "labelFour" with labelString: "labelFour.one"
Show: Label: "labelFour" with labelString: "labelFour.two"
Show: Label: "labelFour" with labelString: "labelFour.three"
Show: Label: "labelFour" with labelString: "labelFour.four"
Show: Label: "labelFour" with labelString: "labelFour.five"
Show: Label: "labelFive" with labelString: "labelFive.one"
Show: Label: "labelFive" with labelString: "labelFive.two"
Show: Label: "labelFive" with labelString: "labelFive.three"
Show: Label: "labelFive" with labelString: "labelFive.four"
Show: Label: "labelFive" with labelString: "labelFive.five"
Instead, I see this:
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
Show: Label: "labelFive" with labelString: "labelFive.five"
The argument binding of lambda is happening when the lambda is executed, rather than when the lambda is created. That is at least unexpected and I think arguably wrong.
I think lambda, as limited as it is, is supposed to create a CLOSURE -- its entire purpose in life is to capture the state its arguments at the time it is created, so that they can be used later when the lambda is evaluated. That's why it is called a "closure", because it closes over the value of its arguments at creation time.
What am I misunderstanding?
lambda
s are late binding. Try this insteadlambda someArg, ls=lambdaString: print(someArg, ls)
- TheLazyScripter