strange behavor....
Mark Wooding
mdw at distorted.org.uk
Mon Nov 15 05:52:31 EST 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