Odd closure issue for generators
Gabriel Genellina
gagsl-py2 at yahoo.com.ar
Thu Jun 4 19:02:35 EDT 2009
En Thu, 04 Jun 2009 18:40:07 -0300, Brian Quinlan <brian at sweetapp.com>
escribió:
> This is from Python built from the py3k branch:
It's not new; same thing happens with 2.x
A closure captures (part of) the enclosing namespace, so names are
resolved in that environment even after the enclosing block has finished
execution.
As always, the name->value evaluation happens when it is required at
runtime, not earlier ("late binding"). So, in the generator expression
(lambda : i for i in range(11, 16)) the 'i' is searched in the enclosing
namespace when the lambda is evaluated, not when the lambda is created.
Your first example:
> [1] >>> c = (lambda : i for i in range(11, 16))
> [2] >>> for q in c:
> [3] ... print(q())
> ...
> 11
> 12
> 13
> 14
> 15
> >>> # This is expected
This code means:
[1] create a generator expression and name it `c`
[2] ask `c` for an iterator. A generator is its own iterator, so returns
itself.
Loop begins: Ask the iterator a new item (the first one, in fact). So the
generator executes one cycle, yields a lambda expression, and freezes.
Note that the current value of `i` (in that frozen environment) is 11.
Name the yielded object (the lambda) `q`.
[3] Call the q object. That means, execute the lambda. It returns the
current value of i, 11. Print it.
Back to [2]: ask the iterator a new item. The generator resumes, executes
another cycle, yields another lambda expression, and freezes. Now, i is 12
inside the frozen environment.
[3] execute the lambda -> 12
etc.
Your second example:
> [4] >>> c = (lambda : i for i in range(11, 16))
> [5] >>> d = list(c)
> [6] >>> for q in d:
> [7] ... print(q())
> ...
> 15
> 15
> 15
> 15
> 15
> >>> # I was very surprised
[4] creates a generator expression same as above.
[5] ask for an iterator (c itself). Do the iteration NOW until exhaustion,
and collect each yielded object into a list. Those objects will be
lambdas. The current (and final) value of i is 15, because the range()
iteration has finished.
[6] iterate over the list...
[7] ...and execute each lambda. At this time, `i` is always 15.
> Looking at the implementation, I see why this happens:
> >>> c = (lambda : i for i in range(11, 16))
> >>> for q in c:
> ... print(id(q.__closure__[0]))
> ...
> 3847792
> 3847792
> 3847792
> 3847792
> 3847792
> >>> # The same closure is used by every lambda
...because all of them refer to the same `i` name.
> But it seems very odd to me and it can lead to some problems that are a
> real pain in the ass to debug.
Yes, at least if one is not aware of the consequences. I think this (or a
simpler example) should be explained in the FAQ. The question comes in
this list again and again...
--
Gabriel Genellina
More information about the Python-list
mailing list