简体   繁体   中英

What happens to `for i in range()` when iterator is overwritten in nested while loop?

What happens to for i in range() when iterator is overwritten in nested while loop? For example, why do the following snippets give different output? When I change the name of the variables i and j inside the while loop, the snippet behaves as I expected. However, when the while loop overwrites i and j , the for loop is affected. Is the resulting behavior of the for loop when its iterator is overwritten in the while predictable?

(a)

for i in range (0,3):
    for j in range (0,3):
         print "after nested for i,j",i,j
         counter = 0
         while counter < 3:
                counter += 1
                i = counter
                j = counter

has o/p:

after nested for i,j 0 0
after nested for i,j 3 1
after nested for i,j 3 2
after nested for i,j 1 0
after nested for i,j 3 1
after nested for i,j 3 2
after nested for i,j 2 0
after nested for i,j 3 1
after nested for i,j 3 2

(b) (the same code with the while commented)

for i in range (0,3):
    for j in range (0,3):
         print "after nested for i,j",i,j

has o/p

after nested for i,j 0 0
after nested for i,j 0 1
after nested for i,j 0 2
after nested for i,j 1 0
after nested for i,j 1 1
after nested for i,j 1 2
after nested for i,j 2 0
after nested for i,j 2 1
after nested for i,j 2 2

Your terminology is slightly wrong. In a for loop you have no access to an iterator. The iterator is kept hidden, behind the scenes. The following looping structures are equivalent.

for i in range(10):
    print(i)

it = iter(range(10)):
while True:
    try:
        i = next(it)
    except StopIteration:
        break
    print(i)

As you can see the iterator object ( it ) is kept hidden in the for loop. It's possible to expose the iterator in a for loop, but that's a different question.

What you are talking about is the name that the elements of the iterable are stored in. If you write over that name during the course of your loop, then that value will simply be ignored at the start of the next iteration of the loop. This is easy to see in the while version of the looping structure where the first thing that is done is that the name i is assigned the next element returned by the iterator.whil

I'm not sure of the purpose of your code, but it is possible to change the state of the iterator you are using. To do this you must write a coroutine . A coroutine is a specialised generator that is able to accept input.

def generator_range(start, end, step=1):
    "Simplified version of the range/xrange function written as a generator."
    counter = start
    while counter < end:
        yield counter 
        counter += step

def coroutine_range(start, end, step=1):
    "Special version of range that allows the internal counter to set."
    counter = start
    while counter < end:
        sent = yield counter 
        if sent is None:
            counter += step
        else:
            counter = sent

For simple range usages the generator version acts the same.

eg.

assert list(range(0, 10)) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
assert list(range(0, 10)) == list(generator_range(0, 10))
assert list(range(0, 10)) == list(coroutine_range(0, 10))

But we can do more complicated looping algorithms with the coroutine.

eg.

# skip numbers in range 3-7 inclusive
l = []
co = coroutine_range(0, 10)
item_to_send = None
while True:
    try:
        i = co.send(item_to_send)
        # if item_to_send is None then the above is the same as next(co)
        item_to_send = None
    except StopIteration:
        break
    if 3 <= i <= 7:
        item_to_send = 8
    else:
        l.append(i)

assert l == [0, 1, 2, 8, 9]

I think this is really a question of variable scope. i and j are in the local function namespace. While loops do not create new namespaces, so in the code fragment

     while len(list) < 2:
            i = i
            j = j

i and 'j' are still the same variables in the local namespace and all you did was reassign them to themselves. You didn't create new i or j . Its harmless, but those assignments should be removed.

Later, at the bottom of the while loop, when you do

            i = PixelCoord[Lightest[1]][0]
            j = PixelCoord[Lightest[1]][1] 

You reassign the i and j in the local function namespace that were holding the iterated values from the for loops to something else. The is relatively benign for j because you return to the inner for loop and j is reassigned the next iterated value. But it's a problem for x because it will hold the changed values until the outer for is reached again.

You can see the problem clearly with a couple of print statements

for i in range (1,array.shape[0]-1):
    print('outer for, i is', i)
    for j in range (1,array.shape[1]-1):
        print('inner for, i and j are', i, j)

The solution is to use different variable names.

If I am clear about your question, then here is solution:

Variables defined in a function have Function Scope and are only visible in the body of the function. You can use same name in different function.

Code 1:

def VAR1():
    var = 'foo'
    def inner():
        var = 'bar'
        print 'inside function, var is ', var
    inner()
    print 'outside function, var is ', var

VAR1()

Output:

inside function, var is  bar
outside function, var is  foo

But in a single function, variables are local. You can't use same name in different place

Code 1:

def VAR():
    var = 'foo'
    if True:
        var = 'bar'
        print 'inside if, var is ', var
    print 'outside if, var is ', var

VAR()

Output:

inside if, var is  bar
outside if, var is  bar

Read more Python's namespaces, scope resolution

This illustrates what is going on in your case(a) :

for i in range(0,2):
    for j in range(0,2):
        print(i,j)
        i = 'new-i'
        j = 'new-j'
        print('   ', i,j)

With output:

0 0                 # i and j set by the for statements
    new-i new-j     # i and j set by the inner assignments
new-i 1             # new j set by its for; i unchanged
    new-i new-j
1 0                 # new i and j set by for
    new-i new-j
new-i 1
    new-i new-j

There's nothing special about the i and j variables, except that they get new values at the start of their respective loops. And after the loops, i and j will have their last values within the loops, in this case new-i and new-j .

For simple loops like this, you can fiddle with the the value of i all you want, and it won't mess up the iteration (that is, the action of the for statement). But since it can confuse you and your algorithm (and your readers), it is generally not a good idea to reassign for iteration variables.


To fully separate your iteration variables from changes within the the while loop, you need to define a function like this:

def foo(i,j):
     print("after nested for i,j",i,j)
     counter = 0
     while counter < 3
         counter += 1
         i = counter
         j = counter

for i in range(3):
    for j in range(3):
        foo(i,j)

producing:

after nested for i,j 0 0
after nested for i,j 0 1
...
after nested for i,j 2 1
after nested for i,j 2 2

If on the other hand, you want changes in the inner while to control the iteration of i you need to use while in the outer loop as well:

i = 0
while i<3:
    j = 0
    while j<3:
        print("after nested for i,j",i,j)
        counter = 0
        while counter < 3:
            counter += 1
            i = counter
            j = counter

which only prints once ( i and j both jump to 3)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM