strange behavor....

Mark Wooding mdw at distorted.org.uk
Mon Nov 15 11:52:31 CET 2010


Steven D'Aprano <steve at REMOVE-THIS-cybersource.com.au> writes:

> >         def foo():
> >           l = []
> >           for i in xrange(10):
> >             (lambda j: l.append((lambda: i, lambda: j)))(i)
> >           print [(f(), g()) for f, g in l]
>
> Here's a slightly less condensed version that demonstrates the same 
> behaviour, but may be slightly easier to understand:

The output is the same but the reasons for it are different:

> def spam():
>     a = []
>     b = []
>     for i in xrange(5):
>         a.append(lambda: i)
>         b.append(lambda i=i: i)

Here, you've replaced lambda-binding by a default argument value.

>     print [f() for f in a]
>     print [f() for f in b]
>
> In your function foo, the name "i" is bound to the objects 0, 1, 2, ... 9 
> sequentially, each time around the for-loop. Inside the loop, a function 
> object is created and then called:
>
> (lambda j: l.append((lambda: i, lambda: j)))(i)
>
> (Aside: this depends on scoping rules that were introduced quite late in 
> Python's history -- prior to version 2.2, this wouldn't work at all.)

Indeed.  The default-argument trick above was used to simulate it.  Not
coincidentally, it was around version 2.2 that Python became interesting
to me...

> The "outer" lambda:
>
>     lambda j: l.append(...)
>
> has a name in the function namespace (a "local variable") called "j". On 
> entry to this function, this j is bound to the same object which is bound 
> to i *at the time that the function is called*. That is, each of the 
> sequence of "outer" lambdas get a distinct j = 0, 1, 2, ... 

Ahh.  You've invented a new concept of localness instead, which is
equivalent to the usual notion of binding.

> None of the function A0, A1, A2, ... have any local variable i.

i.e., i is free in the An functions and in the outer lambda...

> Functions B0, B1, B2, ... similarly have no local variable j. When you 
> call any of the Bs, Python looks in the enclosing scopes for a variable 
> j. However, in this case it *does* find one, in the "outer" lambda,

... but j is bound in the outer lambda.

This is not new terminology: it goes back to lambda calculus: a variable
x occurs free in a term T if

  * T is x;
  * T is U V where x occurs free in U or V; or
  * T is lambda y.U where y is not x and x occurs free in U.

If x occurs free in T then it occurs bound in lambda x.T.

See also SICP.

> >   * Python's `for' loop works by assignment.  The name `i' remains bound
> >     to the same storage location throughout; 
>
> This is not necessarily true. It's true for CPython, where function 
> locals are implemented as fixed slots in the function object; since the 
> slot doesn't move relative to the function, and the function doesn't move 
> relative to the heap, the name i is in a fixed storage location for the 
> life of foo. But that's not necessarily the case for all implementations 
> -- locals could be stored in a data structure that moves data around 
> (say, a red-black tree), or objects could be free to move in the heap, or 
> both.

Don't be silly.  In a tree, the `location' would be the tree node
containing the corresponding key.  You're working at the wrong level of
abstraction.

-- [mdw]



More information about the Python-list mailing list