En Fri, 30 Jan 2009 07:08:29 -0200, Hendrik van Rooyen <m...@microcorp.co.za> escribió:

"Gabriel Genellina" <gagsl-...@yahoo.com.ar> wrote:

Of course this is clearly stated in the Language Reference "Variables used
in the generator expression are evaluated lazily in a separate scope when
the next() method is called for the generator object (in the same fashion
as for normal generators). However, the in expression of the leftmost for
clause is immediately evaluated in the current scope..." -- but this
behaviour is still surprising and not obvious to me. ("not obvious" means
that things could have been different, choosing this was a design
decision).

I am not so sure that it could have been done differently -
I see it something like this:  (going back to almost your
original example, and reversing the position of globals
and locals to make it shorter)

def foo(things):
 for thing in things:
  yield thing()    #it is obvious this is in the local scope of foo

boo = foo([locals,globals])
boo.next()
{'thing': <built-in function locals>, 'things': [<built-in function locals>,
<built-in function globals>]}

and so it is, when you feed it the locals function
[...]
Now I don't think that you could really do it differently -
the right hand side of the generator expression is exactly
like my passed argument "things", in all cases as far as
I can see, and this means that the right hand side is
evaluated when it is "passed", and the left hand side
is whatever is done in the "for thing in things:" loop.
All the generator expression does is that it saves you
the trouble of defining the function - it kind of does it
for you, and calls it, and returns the generator object,
and throws the function away, all in one hit. (this is not
necessarily the real mechanism, but the effect is exactly
as if it were)

Yes, but this is not the only alternative. You *decided* that foo and bar will take one argument - this means that it uses early binding and it is evaluated when the generator expression is created. This is a design decision, and it could have been different. The left-part of the generator expression (in the function analogy, the "yield" expression) is late bound - it is completely evaluated at every iteration, using whatever values are currently bound to external (free) variables. The same *could* happen with the right part too -- although this is not what was decided.

Consider this expression: g = (x+A for x in L for y in M). This is currently expanded more or less like this:

def foo(L):
  for x in L:
    for y in M:
      yield x+A
g = foo(iter(L))

(as your example above) Note that L has a special status -- it's the only expression evaluated at the time g is defined. It *could* have been like this:

def foo()
  for x in L:
    for y in M:
      yield x+A
g = foo()

or even like this:

def foo(L, M, A):
  for x in L:
    for y in M:
      yield x+A
g = foo(iter(L), iter(M), A)

In particular, I like the 2nd (all late binding). Seems this topic was discussed many times [1] when PEP289 [2] was proposed, and "practicality beats purity".

[1] http://mail.python.org/pipermail/python-dev/2004-April/date.html
[2] http://www.python.org/dev/peps/pep-0289/

--
Gabriel Genellina

--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to