Lambda Scoping

Alex Martelli aleax at aleax.it
Mon Apr 29 11:52:37 EDT 2002


Gerson Kurz wrote:

> The following code
> ...
> funcs = []
> for instance in range(10):
>     funcs.append( lambda: instance )
> print map(apply,funcs)
> 
> funcs = []
> for instance in range(10):
>     funcs.append( lambda instance=instance: instance )
> print map(apply,funcs)
> ...
> prints
> 
> [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
> 
> which I sort-of understand (I figure, in "lambda: instance" instance
> is a lazy expression; "lambda instance=instance: instance" the
> expression itself - "instance" - is a lazy expression, too, but the
> argument list is not.) However, can anybody please explain this more
> detailed?

It's not really connected to lambdas -- you'll get the same effects with
any nested function.  Anyway, if you prefer to discuss it with lambda...:

In the case:
lambda: instance
you have an argumentless callable which, when called, returns the
value of a global or free variable named 'instance' at the time of
the call.  In this case, the calls all happen when instance is
worth 10.

In the case (which you didn't try but is handy for explaining:-):
lambda flippo=instance: flippo
you have an 'argumentless' callable (actually has one optional
argument, but let's forget that:-) with a local variable 'flippo'
that is initialized at definition-time with the value that the
global or free variable named 'instance' happens to have at the
time of the definition.  When called, this callable returns the
value of flippo -- so overall, it returns the value that global
or free variable 'instance' had at the time the callable was
defined.

In other words, an argument with a default value 'snapshots' the
expression at the time of definition.

In the case
lambda instance=instance: instance
you have named the local variable 'instance' rather than 'flippo'
but the name of the local makes no difference, so the treatment
of the second ('missing') case, given above, fully applies.


With real functions, the first case, with which lambda was:
> funcs = []
> for instance in range(10):
>     funcs.append( lambda: instance )
> print map(apply,funcs)
you could translate almost-indifferently to either a more direct
equivalent:

funcs = []
for instance in range(10):
    def patakun(): return instance
    funcs.append( patakun )
print map(apply,funcs)

which like your first case creates and appends ten different
callables, but all behaving identically; or:

funcs = []
def patakun(): return instance
for instance in range(10):
    funcs.append( patakun )
print map(apply,funcs)

which appends 10 references to the same callable instead.


The second case, which in your code was (except for the
name of the callable's local variable):

> funcs = []
> for instance in range(10):
>     funcs.append( lambda instance=instance: instance )
> print map(apply,funcs)

you HAVE to translate into the building of 10 separate
callables, because each carries a different 'snapshot'
in its default-argument:

funcs = []
for instance in range(10):
    def palok(flippo=instance): return flippo
    funcs.append( palok )
print map(apply,funcs)

Here, if you try to move the def out of the loop, the
def fails, because it needs to evaluate 'instance' in
global or outer scope when 'instance' isn't bound yet
(assuming this snippet is all of the program -- else,
the name may have a stray binding remaining from some
previous part of the program).



Hope this has helped rather than confused...

Alex




More information about the Python-list mailing list