[Python-ideas] Changing semantics of for-loop variable

Nick Coghlan ncoghlan at gmail.com
Fri Sep 30 17:07:38 CEST 2011


On Fri, Sep 30, 2011 at 6:03 AM, Greg Ewing <greg.ewing at canterbury.ac.nz> wrote:
> Nick Coghlan wrote:
>> If we did this, I think we'd have to make reusing a nonlocal reference
>> as a loop variable a SyntaxError
>
> That might be a bit harsh, since it would make currently valid
> code illegal.
>
> Another possibility would be to say that the new semantics only
> apply when the loop variable is local; if it's declared nonlocal
> or global, the old semantics apply.

Yeah, I think if we decide how to handle the global case, then the
same answer can be applied in the nonlocal case.

The local case would be straightforward:

    def func_gen():
        for i in range(3):
            def inner():
                return i
            yield i, inner

>>> [i, f() for i, f in list(func_gen())]  # Today reports [(0, 2), (1, 2), (2, 2)]
[(0, 0), (1, 1), (2, 2)]

To maintain semantic equivalence between for loops and while loops,
the new semantics would need to affect *all* name binding inside loops
for names referenced from inner scopes, not just the iteration
variable in for loops (FWIW, this would likely make implementation
easier rather than harder):

    def func_gen():
        i = 0
        while i < 3:
            def inner():
                return i
            yield i, inner
            i += 1

>>> [i, f() for i, f in list(func_gen())]  # Today reports [(0, 2), (1, 2), (2, 2)]
[(0, 0), (1, 1), (2, 2)]

Explicit nonlocal and global declarations would then override the new
semantics completely, just as they override ordinary local semantics
today. While locals would gain early binding semantics, declared
globals and nonlocals would retain late binding semantics:

    def func_gen():
        global i
        for i in range(3):
            def inner():
                return i
            yield i, inner

>>> [i, f() for i, f in list(func_gen())]  # Remains unchanged under new regime
[(0, 2), (1, 2), (2, 2)]
>>> i
2

Code that used the default argument hack would continue to work under
the new regime. Code that deliberately exploited the current late
binding semantics would break.

I think I'm still overall -1 on the idea, since it creates some rather
subtle weirdness with the hidden switch to early binding semantics for
locals, but the retention of late binding semantics for nonlocals and
globals. While late binding for locals has its problems, it at least
has the virtues of consistency and familiarity.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia



More information about the Python-ideas mailing list