Odd name shadowing in comprehension

Steve D'Aprano steve+python at pearwood.info
Sun Oct 23 02:26:08 EDT 2016


On Sun, 23 Oct 2016 02:24 pm, Chris Angelico wrote:

> 1) Sometimes, all the iterables can be evaluated in advance.
> 
> dice_2d6 = [a+b for a in range(1,7) for b in range(1,7)]
> 
> 2) But sometimes, subsequent iterables depend on the outer loop.
> 
> triangle = [a+b for a in range(1, 7) for b in range(1, a+1)]
> 
> So in case #2, you cannot evaluate the second range until you're
> actually in the loop 

Obviously not.


> - but in both cases, the first one can be pre-evaluated. 

That doesn't follow. Consider:

gen = (x for x in [time.time()])

Should next(gen) return the time that the generator expression was created,
or the time when you first call next()? The answer to that depends on
whether you want early binding or late binding. If that's not clear,
consider this instead:

y = 'first'
gen = (x for x in [y])
y = 'second'

What will next(gen) return? With *early binding*, it will return "first".
This is how function parameter defaults work. With *late binding*, it will
return "second", which is how access to globals normally work.

But that's a separate issue to the question of what happens if y is a local
variable:

gen = (x for x in [y] for y in [999])

I can see how it happens and why it happens, but I think it is still weird
and deserves at least a warning.



> Also, the most-outside loop's iterable gets evaluated 
> exactly once, whereas inner loops might be evaluated more often (or
> less, for that matter).

Again, that's quite straight forward.


> Only in extreme edge cases involving name scoping can the evaluation
> of the first iterable depend on whether it's inside or outside the
> invisible function.

I don't think that this is an extreme edge case.





-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.




More information about the Python-list mailing list