Creating lambdas inside generator expression
Chris Angelico
rosuav at gmail.com
Wed Jun 29 17:17:43 EDT 2022
On Thu, 30 Jun 2022 at 02:49, Johannes Bauer <dfnsonfsduifb at gmx.de> wrote:
> But now consider what happens when we create the lambdas inside a list
> comprehension (in my original I used a generator expresison, but the
> result is the same). Can you guess what happens when we create conds
> like this?
>
> conds = [ lambda msg: msg.hascode(z) for z in ("foo", "bar") ]
>
> I certainly could not. Here's what it outputs:
>
> Check for bar
> False
> Check for bar
> False
>
> I.e., the iteration variable "z" somehow gets bound inside the lambda
> not by its value, but by its reference. All checks therefore refence
> only the last variable.
>
Yep, that is the nature of closures. (Side point: This isn't actually
a generator expression, it's a list comprehension; current versions of
Python treat them broadly the same way, but there was previously a
difference in the way scoping worked.) What you're seeing is a
consequence of the way that closures work, and it is a very good thing
most of the time :)
The usual way to "snapshot" a variable is what you showed in your
followup: a default argument value.
def f(..., z=z):
... z has been snapshot
(As others have pointed out, this isn't unique to lambdas; any
function will behave that way.)
Antoon offered another variant, but written as a pair of lambda
functions, it's a little hard to see what's going on. Here's the same
technique written as a factory function:
def does_it_have(z):
return lambda msg: msg.hascode(z)
conds = [does_it_have(z) for z in ("foo", "bar")]
Written like this, it's clear that the variable z in the comprehension
is completely different from the one inside does_it_have(), and they
could have different names if you wanted to. This is a fairly clean
way to snapshot too, and has the advantage that it doesn't pretend
that the function takes an extra parameter.
ChrisA
More information about the Python-list
mailing list