A gotcha: Python pain point?

Terry Reedy tjreedy at udel.edu
Tue Jun 12 04:46:19 EDT 2007


"James Stroud" <jstroud at mbi.ucla.edu> wrote in message 
news:f4knb2$mn6$1 at zinnia.noc.ucla.edu...
| Beorn wrote:
| > Consider this example:
| >
| >   >>> def funcs(x):
| >   ...     for i in range(5):
| >   ...         def g(): return x + i
| >   ...         yield g
| >
| >   >>> [ fun() for fun in list(funcs(1)) ]
| >   [5, 5, 5, 5, 5]
| >
| > Whereas:
| >
| >   >>> [ fun() for fun in funcs(1) ]
| >   [1, 2, 3, 4, 5]

| If this isn't classified as a bug,

It is not, it is well-documented behavior.  Still, suggestions for 
improvement might be considered.

| Why would it be desirable for a generator to behave
| differently in two different contexts.

I have no idea.

Each call of the generator function funcs behaves the same.  It returns a 
generator that yields 5 identical copies of the inner function g.  The 
multiple copies are not needed and only serve to confuse the issue. 
Changing funcs to return a generator that yields the *same* function (five 
times) gives the same behavior.

def funcs(x):
    def g(): return x + i
    for i in range(5):
        yield g

print [ fun() for fun in list(funcs(1)) ]
print [ fun() for fun in funcs(1) ]

>>> # when run
[5, 5, 5, 5, 5]
[1, 2, 3, 4, 5]

What matters is the value of g's nonlocal var i (funcs' local var i) when 
the yielded function g is *called*.

The difference between returning versus yielding an inner closure such as g 
is this.  If g is returned, the outer function has terminated and the 
enclosed variable(s), i in this case, is frozen at its final value.  If g 
is yielded, the enclosed i is *live* as long as the generator is, and its 
values can change between calls, as in the second print statement.

| Should I import this to see how
| many principles this behavior violates?

???

If you meant 'report' (on SF), please do not.

Terry Jan Reedy






More information about the Python-list mailing list