0

This code is perfectly valid Python

x=[[1,2,3,4], [11,22,33,44]]
for e in x:
    for e in e:
        print e

Can someone please tell me why, and how this works?

I understand that both the e's are in different scopes, but how come using them together like this isn't causing an error?

3
  • 3
    Why would it cause an error? Commented Nov 6, 2012 at 16:40
  • What error would it cause, were it supposed to? Commented Nov 6, 2012 at 16:40
  • ha! good question. it is something I hadn't realized about Python until I was debugging and noticed that I did it...I was worried I had done something terrible but it turned out just fine. Still not so sure how I feel about it being "legal". Commented Apr 17, 2015 at 23:14

6 Answers 6

9

The scopes aren't different; in Python a function has a single local scope (as does code entered at global level in the console).

The reason the code is OK is that you finish using the outer value of e before you rebind it to the inner values; try looking at what this prints:

x=[[1,2,3,4], [11,22,33,44]]
for e in x:
    for e in e:
        print e
    print e
Sign up to request clarification or add additional context in comments.

Comments

3

e is just a label. Each iteration of your outer loop, e is assigned the nth value from x, and each inner loop iteration it is assigned the mth value from x[n]. It's perfectly valid Python code, it is just not advisable from a style perspective because outside of a trivial example, it can quickly become confusing what e represents at what point in the code, and thus is likely to lead to bugs.

Comments

3

I suppose you could roughly translate the inner loop in that above expression to:

for e in x:
    ee = iter(e)
    try:
        e = next(ee)
        while True
            print e
            e = next(ee)
    except StopIteration
        pass

Note that the key thing here is in the statement: for e in ..., ... is converted to an iterator via the iterator protocol. The object you actually iterate over is a separate object from the e you gave it initially. Since it's a separate object (stored separately from its name in the current scope to allow it to be iterated over) there is no problem with binding a new variable to that name in the current scope -- Maybe I should say that there is no problem other than it makes the code really hard to follow.

It's effectively the same reason you don't have a problem doing this:

A = [['foo']]  #Define A
b = A[0]       #Take information from A and rebind it to something else
c = A          #We can even take the entire reference and bind/alias it to a new name.
A = 'bar'      #Re-assign A -- Python doesn't care that A already existed.

Here are a couple more things to think about:

x = [1,2,3,4]
for a in x:
    print a
    next(a)   #Raises an error because lists aren't iterators!

Now a seldom used, (but sometimes necessary) idiom:

x = [1,2,3,4]
y = iter(x)   #create an iterator from the list x
for a in y:
    print a
    #This next line is OK.  
    #We also consume the next value in the loop since `iter(y)` returns `y`!
    #In fact, This is the easiest way to get a handle on the object you're
    #actually iterating over.
    next(y)   

finally:

x = [1,2,3,4]
y = iter(x)   #create an iterator from the list x
for a in y:
    print a
    #This effectively does nothing to your loop because you're rebinding
    #a local variable -- You're not actually changing the iterator you're
    #iterating over, just as `A = 'bar'` doesn't change the value of
    #the variable `c` in one of the previous examples.
    y = iter(range(10))

5 Comments

I think this answer is a bit misleading in the sense that it seems to imply that there are multiple scopes.
@NullUserException -- How does it imply there are separate scopes? I'm happy to update if you have a suggestion about what specifically is confusing. The point I was trying to make is that at the time the for loop evaluated, python constructs an iterator out of the object on the far right and uses that for the rest of the loop and it doesn't matter what you do to the variable name which held a reference to the object that was used to construct the iterator. Unfortunately, there are no clean semantics (that I can come up with) to say it better ...
@NullUserException -- In some ways, the iterator which you're actually iterating over does exist in a different scope since you can't get a handle on it unless iter(obj) returns obj in which case you have a handle on it only because you have a handle on obj.
Nice account of the inner workings of a for loop. A footnote: for further confirmation of this, try disassembling a for loop; you'll see the GET_ITER opcode, which effectively calls iter on the top of the stack.
@senderle -- It's been a long time since I actually thought about how for loops work (maybe a year since I actually tried to make a class with __iter__...). It was kind of fun to realize that I now have a much better understanding of how all this stuff works than I did back then.
1

Because second e binds after first e evaluates to list. So, all other iteration steps take items not from variable, but from list. For example, in next code reassinging to e has no effect on iteration:

for e in x:
    for i in e:
        print i
        e = [8, 8, 8]

Comments

0

Frankly, I did not find any of the current answers to this question to be satisfying. I think the underlying explanation is that Python handles for-loop scoping as a special case.

It looks like Python recognizes that a for-loop variable is special and, at the conclusion of the for-loop, copies the current value of the variable to the enclosing scope, as needed.

This explains why you can write the following code:

for i in range(3):
    i = 5
    print("hello")

and it will execute 3 times. The for-loop variable i is special and distinct from the variable i that is assigned the value 5.

Python probably sees it as something like:

for i_12349678 in range(3):
    i = 5
    print("hello")

And for nested for-loops that use the same variable i:

for i in range(3):
    for i in range(4):
        print("i = %s" % i)

Python probably sees it more like:

for i_18987982221 in range(3):
    for i_9870272721 in range(4):
        print("i = %s" % i_9870272721)

For more information: http://mail.python.org/pipermail/python-ideas/2008-October/002109.html

Comments

0

'e' in this case are in the same scope. If you do something like...

for i in range(MAX):
    if(some_condition == True):
        for i in range(5):
            #do stuff

If the code traverses into the inner for loop, it'll increment the "outer i" 5 times, causing you to skip out on those runs.

With the code you posted, it doesn't produce a syntax error, and it happens to work out from a logical perspective, but there may be other examples where you will get the wrong result.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.