54

Here's the Python code I'm having problems with:

for i in range (0,10):
    if i==5:
        i+=3
    print i

I expected the output to be:

0
1
2
3
4
8
9

However, the interpreter spits out:

0
1
2
3
4
8
6
7
8
9

I know that a for loop creates a new scope for a variable in C, but have no idea about Python. Why does the value of i not change in the for loop in Python, and what's the remedy to it to get the expected output?


See How to modify list entries during for loop? for how to modify the original sequence. In 3.x, of course, range creates an immutable object, so it would still not work.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
firecast
  • 988
  • 2
  • 10
  • 20

10 Answers10

57

The for loop iterates over all the numbers in range(10), that is, [0,1,2,3,4,5,6,7,8,9].
That you change the current value of i has no effect on the next value in the range.

You can get the desired behavior with a while loop.

i = 0
while i < 10:
    # do stuff and manipulate `i` as much as you like       
    if i==5:
        i+=3

    print i

    # don't forget to increment `i` manually
    i += 1
Junuxx
  • 14,011
  • 5
  • 41
  • 71
27

Analogy with C code

You are imagining that your for-loop in python is like this C code:

for (int i = 0; i < 10; i++)
    if (i == 5)
        i += 3;

It's more like this C code:

int r[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (int j = 0; j < sizeof(r)/sizeof(r[0]); j++) {
    int i = r[j];
    if (i == 5)
        i += 3;
}

So modifying i in the loop does not have the effect you expect.

Disassembly example

You can look at the disassembly of the python code to see this:

>>> from dis import dis
>>> def foo():
...     for i in range (0,10):
...         if i==5:
...             i+=3
...         print i
... 
>>> dis(foo)
  2           0 SETUP_LOOP              53 (to 56)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (0)
              9 LOAD_CONST               2 (10)
             12 CALL_FUNCTION            2
             15 GET_ITER            
        >>   16 FOR_ITER                36 (to 55)
             19 STORE_FAST               0 (i)

  3          22 LOAD_FAST                0 (i)
             25 LOAD_CONST               3 (5)
             28 COMPARE_OP               2 (==)
             31 POP_JUMP_IF_FALSE       47

  4          34 LOAD_FAST                0 (i)
             37 LOAD_CONST               4 (3)
             40 INPLACE_ADD         
             41 STORE_FAST               0 (i)
             44 JUMP_FORWARD             0 (to 47)

  5     >>   47 LOAD_FAST                0 (i)
             50 PRINT_ITEM          
             51 PRINT_NEWLINE       
             52 JUMP_ABSOLUTE           16
        >>   55 POP_BLOCK           
        >>   56 LOAD_CONST               0 (None)
             59 RETURN_VALUE        
>>> 

This part creates a range between 0 and 10 and realizes it:

          3 LOAD_GLOBAL              0 (range)
          6 LOAD_CONST               1 (0)
          9 LOAD_CONST               2 (10)
         12 CALL_FUNCTION            2

At this point, the top of the stack contains the range.

This gets an iterator over the object on the top of the stack, i.e. the range:

         15 GET_ITER  

At this point, the top of the stack contains an iterator over the realized range.

FOR_ITER begins iterating over the loop using the iterator at the top of th estack:

    >>   16 FOR_ITER                36 (to 55)

At this point, the top of the stack contains the next value of the iterator.

And here you can see that the top of the stack is popped and assigned to i:

         19 STORE_FAST               0 (i)

So i will be overwritten regardless of what you do in the loop.

Here is an overview of stack machines if you haven't seen this before.

hughdbrown
  • 47,733
  • 20
  • 85
  • 108
17

A for loop in Python is actually a for-each loop. At the start of each loop, i is set to the next element in the iterator (range(0, 10) in your case). The value of i gets re-set at the beginning of each loop, so changing it in the loop body does not change its value for the next iteration.

That is, the for loop you wrote is equivalent to the following while loop:

_numbers = range(0, 10) #the list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_iter = iter(_numbers)
while True:
    try:
        i = _iter.next()
    except StopIteration:
        break

    #--YOUR CODE HERE:--
    if i==5:
        i+=3
    print i
Claudiu
  • 224,032
  • 165
  • 485
  • 680
5

If for some reason you did really want to change add 3 to i when it's equal to 5, and skip the next elements (this is kind of advancing the pointer in C 3 elements), then you can use an iterator and consume a few bits from that:

from collections import deque
from itertools import islice

x = iter(range(10)) # create iterator over list, so we can skip unnecessary bits
for i in x:
    if i == 5:             
        deque(islice(x, 3), 0) # "swallow up" next 3 items
        i += 3 # modify current i to be 8
    print i

0
1
2
3
4
8
9
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
  • 1
    I think the while loop in Junuxx's answer is much tidier and cleaner. This answer is however useful if you can't jump ahead in the sequence with a simple operation (like +=3) – Breezer Feb 19 '16 at 14:18
3

In python 2.7 range function create a list while in python 3.x versions it creates a 'range' class object which is only iterable not a list, similar to xrange in python 2.7.

Not when you are iterating over range(1, 10), eventually you are reading from the list type object and i takes new value each time it reaches for loop.

this is something like:

for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
    if i==5:
        i+=3
    print(i)

Changing the value wont change the iteration order from the list.

pranav dua
  • 51
  • 2
1

I gets reset every iteration, so it doesn't really matter what you do to it inside the loop. The only time it does anything is when i is 5, and it then adds 3 to it. Once it loops back it then sets i back to the next number in the list. You probably want to use a while here.

Hoopdady
  • 2,296
  • 3
  • 25
  • 40
1

Python's for loop simply loops over the provided sequence of values — think of it as "foreach". For this reason, modifying the variable has no effect on loop execution.

This is well described in the tutorial.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
1
it = iter(xrange (0,10))
for i in it:
    if i==4: all(it.next() for a in xrange(3))
    print i

or

it = iter(xrange (0,10))
itn = it.next
for i in it:
    if i==4: all(itn() for a in xrange(3))
    print i
eyquem
  • 26,771
  • 7
  • 38
  • 46
1

You can make the following modification to your for loop:

for i in range (0,10):
    if i in [5, 6, 7]:
        continue
    print(i)
CopyPasteIt
  • 532
  • 1
  • 8
  • 22
0

In my view, the analogous code is not a while loop, but a for loop where you edit the list during runtime:

originalLoopRange = 5
loopList = list(range(originalLoopRange))
timesThroughLoop = 0
for loopIndex in loopList:
    print(timesThroughLoop, "count")
    if loopIndex == 2:
        loopList.pop(3)
        print(loopList)
    print(loopIndex)
    timesThroughLoop += 1
AdityaS
  • 31
  • 6
  • The while loops are infinite if the counter variable is not updated. In other languages, a for loop is not infinite unless somebody messes with the counter. In python, to make a loop which is not normally infinite, and which you can mess with the "counter" to make shorter or longer is more correctly represented (in my opinion) by the code I have posted. You can insert or pop from the list, which increases or decreases the length of the loop. – AdityaS Aug 08 '18 at 05:59
  • If you do nothing, the loop is not infinite. This is thus (in my opinion) more like what the original poster is asking about. Perhaps more importantly, that's what other similar questions are asking about. Though it is "dangerous" in the sense that by inserting into a the loopList, one can make it infinite again. – AdityaS Aug 08 '18 at 05:59