[Python-ideas] For-loop variable scope: simultaneous possession and ingestion of cake

Terry Reedy tjreedy at udel.edu
Sat Oct 4 01:29:33 CEST 2008


Greg Ewing wrote:
> There's been another discussion on c.l.py about
> the problem of

The behavior is a fact.  Calling it a 'problem' is an opinion, one that 
I disagree with.  So to me, your 'solution' is a solution to a non-problem.

>   lst = []
>   for i in range(10):
>     lst.append(lambda: i)
>   for f in lst:
>     print f()
> 
> printing 9 ten times instead of 0 to 9.

If one understands that 'lambda: i' is essentially 'def f(): return i' 
and that code bodies are only executed when called, the behavior is obvious.

> The usual response is to do
> 
>     lst.append(lambda i=i: i)

Here are 5 more alternatives that have the same effect:
(The 3rd is new since my c.l.p response)

lst = []
for i in range(10):
     lst.append(eval("lambda: %d" %i))

lst = []
def f(i): return lambda: i
for i in range(10):
     lst.append(f(i))

lst = []
def f(i):
     lst.append(lambda:i)
for i in range(10):
     f(i)

def populate(n):
   n -= 1
   if n >= 0: return populate(n)+[lambda:n]
   else: return []
lst = populate(10)

def populate(i,n,lst):
     if i < n: return populate(i+1,n,lst+[lambda:i])
     else: return lst
lst = populate(0,10,[])

> but this is not a very satisfying solution.

To you.

> For one thing, it's still abusing default arguments,

Use is a fact, abuse is an opinion.

 > something that lexical scoping was supposed to have removed the
> need for,

Lexical scoping allows reading of variables that vary (get rebound).  In 
2.6/3.0, one can also write this from within the closure.  This addition 
was anticipated from the beginning; it just took a while for Guido to 
decide on the syntax from among the 10-20 proposals.  Modifying 
func.__defaults__ is much more awkward (I somehow thought it to be 
read-only, but in 3.0 it is not.)

 > and it won't work in some situations, such
> as if the function needs to take a variable number of
> arguments.

So use another method.  Are not 5 others enough?

> Also, most other languages which have lexical scoping
> and first-class functions don't seem to suffer from
> problems like this. To someone familiar with one of
> those languages (e.g. Scheme, Haskell) it looks as
> if there's something broken about the way scoping of
> nested functions works in Python.

A respondant on c.l.p pointed out that Python works the same as C and 
Common Lisp.  There are quite a few differences between Scheme/Haskell 
and Python.  I believe neither is as widely known and used as Python.

> So I'd like to propose something that would satisfy
> both requirements:
> 
> 0. There is no change if the loop variable is not
>    referenced by a nested function defined in the loop
>    body. The vast majority of loop code will therefore
>    be completely unaffected.
> 
> 1. If the loop variable is referenced by such a nested
>    function, a new local scope is effectively created
>    to hold each successive value of the loop variable.

As I understand this, you are proposing that

for i in it:
   body

be rewritten as

def _(i):
   body
for i in it:
   _(i)

which is my third alternative above and only takes about 15 additional 
keystrokes, and only those are needed by an anti-default purist. 
Someone who wants this semantic should write it explicitly.

I believe this sort of automagic would make Python even harder to learn 
and understand.  One should be able to learn and use loops and simple 
functions before learning about nested functions and closures.

If the loop is inside a function, as is typical for real code, and the 
loop body rebinds names outside the loop, then automagic addition of 
nonlocal declarations would be needed.

> 2. Upon exiting the loop, the final value of the loop
>    variable is copied into the surrounding scope, for
>    use by code outside the loop body.

My rewrite above does not require this.

[snip]
> The benefit would be that almost all code involving
> loops and nested functions would behave intuitively,

To you, perhaps, but not to all.

> Python would free itself from any remaining perception
> of having broken scope rules,

That is a very idiosyncratic perception.

> and we would finally be
> able to consign the default-argument hack to the garbage
> collector of history.

By ruining the language? Just to save a few keystrokes? No thanks. -1000

(Overblown rhetoric meets overblown rhetoric ;-)

Terry Jan Reedy





More information about the Python-ideas mailing list