
[Tim]
I'm not sure it's "a feature" that
print [n+f() for x in range(10)]
looks up n and f anew on each iteration -- if I saw a listcomp that actually relied on this, I'd be eager to avoid inheriting any of author's code.
[Guido]
It's just a direct consequence of Python's general rule for name lookup in all contexts: variables are looked up when used, not before. (Note: lookup is different from scope determination, which is done mostly at compile time. Scope determination tells you where to look; lookup gives you the actual value of that location.) If n is a global and calling f() changes n, f()+n differs from n+f(), and both are well-defined due to the left-to-right rule. That's not good or bad, that's just *how it is*. Despite having some downsides, the simplicity of the rule is good; I'm sure we could come up with downsides of other rules too.
Sorry, but none of that follows unless you first insist that a listcomp is semantically equivalent to a particular for-loop. Which we did do at the start, and which is now being abandoned in part ("well, except for the for target(s) -- well, OK, they still work like exactly like the for-loop would work if the target(s) were renamed in a particular 'safe' way"). I don't mind the renaming trick there, but by the same token there's nothing to stop explaining the meaning of a generator expression as a particular way of writing a generator function either. It's hardly a conceptual strain to give the function default arguments, or even to eschew that technical implementation trick and just say the generator's frame gets some particular initialized local variables (which is the important bit, not the trick used to get there).
Despite the good case that's been made for what would be most useful,
I don't see that any good case had been made for or against it: the only cases I care about are real use cases. A thing stands or falls by that, purity be damned. I have since posted the first plausible use case that occurred to me while thinking about real work, and "closure semantics" turned out to be disastrous in that example (see other email), while "capture the current binding" semantics turned out to be exactly right in that example. I suspected that would be so, but I still want to see more not-100%-fabricated examples.
I'm loathe to drop the evaluation rule for convenience in one special case. Next people may argue that in Python 3.0 lambda should also do this; arguably it's more useful than the current semantics there too.
It's not analogous: when I'm writing a lambda, I can *choose* which bindings to capture at lambda definition time, and which to leave free. Unless generator expressions grow more hair, I have no choice when writing one of those, so the implementation-forced choice had better be overwhelmingly most useful most often. I can't judge the latter without plausible use cases, though.
And then what next -- maybe all nested functions should copy their free variables?
Same objection as to the lambda example.
Oh, and then maybe outermost functions should copy their globals into locals too -- that will speed up a lot of code. :-)
It would save Jim a lot of thing=thing arglist typing in Zope code too <wink>.
There are other places in Python where some rule is applied to "all free variables of a given piece of code" (the distinction between locals and non-locals in functions is made this way). But there are no other places where implicit local *copies* of all those free variables are taken.
I didn't suggest to copy anything, just to capture the bindings in use at the time a generator expression is evaluated. This is easy to explain, and trivial to explain for people familiar with the default-argument trick. Whenever I've written a list-of-generators, or in the recent example a generator pipeline, I have found it semantically necessary, without exception so far, to capture the bindings of the variables whose bindings wouldn't otherwise be invariant across the life of the generator. It it turns out that this is always, or nearly almost always, the case, across future examples too, then it would just be goofy not to implement generator expressions that way ("well, yes, the implementation does do a wrong thing in every example we had, but what you're not seeing is that the explanation would have been a line longer had the implementation done a useful thing instead" <wink>).
I'd need to find a unifying principle to warrant doing that beyond utility.
No you don't -- you just think you do <wink>.