为什么修改迭代变量不影响后续迭代?

55 投票
10 回答
57277 浏览
提问于 2025-04-17 18:48

这是我遇到问题的Python代码:

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

我原本期待输出是:

0
1
2
3
4
8
9

但是,解释器却输出了:

0
1
2
3
4
8
6
7
8
9

我知道在C语言中,for循环会为变量创建一个新的作用域,但我对Python不太了解。为什么在Python的for循环中,i的值没有变化?有什么办法可以解决这个问题,让我得到预期的输出呢?


想了解如何在for循环中修改列表的内容,可以查看 如何在for循环中修改列表项? 在Python 3.x中,当然,range会创建一个不可变的对象,所以这样做也不会有效。

10 个回答

18

在Python中,for循环其实是一个“遍历每个元素”的循环。在每次循环开始时,i会被设置为迭代器中的下一个元素(在你的例子中是range(0, 10))。每次循环开始时,i的值都会重新设置,所以在循环体内改变i的值,并不会影响下一次循环中的i的值。

也就是说,你写的for循环其实和下面这个while循环是一样的:

_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
28

与C代码的类比

你可以想象你的 for-loop 在Python中就像下面这段C代码:

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

其实更像下面这段C代码:

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;
}

所以在循环中修改 i 并不会产生你预期的效果。

反汇编示例

你可以查看 Python代码的反汇编 来了解这个过程:

>>> 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        
>>> 

这部分 创建了一个从0到10的范围 并实现了它:

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

此时,栈顶包含这个范围。

接下来 获取栈顶对象的迭代器,也就是这个范围:

         15 GET_ITER  

此时,栈顶包含一个指向已实现范围的迭代器。

FOR_ITER开始使用栈顶的迭代器进行循环

    >>   16 FOR_ITER                36 (to 55)

此时,栈顶包含迭代器的下一个值。

在这里你可以看到 栈顶的值被弹出并赋值给 i

         19 STORE_FAST               0 (i)

所以无论你在循环中做什么, i 都会被覆盖。

如果你之前没有见过,这里有一个 栈机器的概述

58

这个for循环会遍历从0到9的所有数字,也就是range(10)生成的数字列表[0,1,2,3,4,5,6,7,8,9]
你改变i的当前值并不会影响下一个要遍历的数字。

如果你想要不同的效果,可以使用while循环。

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

撰写回答