在循环期间更改可迭代变量

时间:2016-07-29 12:09:32

标签: python for-loop

it成为python中的可迭代元素。 在什么情况下,it内部it的变化反映了it = range(6) for i in it: it.remove(i+1) print i ?或者更直接:这样的事情什么时候起作用?

it = range(6)
for i in it:
    it = it[:-2]
    print it

导致打印0,2,4(显示循环运行3次)。

另一方面呢

[0,1,2,3]
[0,1]
[]
[]
[]
[],

导致输出:

it = range(6)
for i in it:
    it = it.remove(i+1)
    print it

显示循环运行6次。我想它与就地操作或可变范围有关,但不能100%肯定地围绕它。

澄清:

一个例子,不起作用:

start_urls

导致'None'被打印并且Error(NoneType没有属性'remove')被抛出。

3 个答案:

答案 0 :(得分:6)

当您迭代list时,实际上会调用list.__iter__(),这会返回绑定到listiterator的{​​{1}}对象,然后实际迭代list }。从技术上讲,这个:

listiterator

实际上是一种语法糖:

itt = [1, 2, 3]
for i in itt:
    print i

所以在这一点 - 在循环中 - 重新绑定itt = [1, 2, 3] iterator = iter(itt) while True: try: i = it.next() except StopIteration: break print i 不会影响itt(这使它自己对列表的引用),但变异 { {1}}显然会影响它(因为两个引用都指向同一个列表)。

嘿,重新绑定和变异之间存在同样的旧区别......没有listiterator循环,你会得到相同的行为:

itt

答案 1 :(得分:4)

在第一个循环中,您正在更改it对象(对象的内部状态),但是,在第二个循环中,您将it重新分配给另一个对象,保持初始对象不变。 / p>

让我们看看生成的字节码:

In [2]: def f1():
   ...:     it = range(6)
   ...:     for i in it:
   ...:         it.remove(i + 1)
   ...:         print i
   ...:         

In [3]: def f2():
   ...:     it = range(6)
   ...:     for i in it:
   ...:         it = it[:-2]
   ...:         print it
   ...:         

In [4]: import dis

In [5]: dis.dis(f1)
  2           0 LOAD_GLOBAL              0 (range)
              3 LOAD_CONST               1 (6)
              6 CALL_FUNCTION            1
              9 STORE_FAST               0 (it)

  3          12 SETUP_LOOP              36 (to 51)
             15 LOAD_FAST                0 (it)
             18 GET_ITER            
        >>   19 FOR_ITER                28 (to 50)
             22 STORE_FAST               1 (i)

  4          25 LOAD_FAST                0 (it)
             28 LOAD_ATTR                1 (remove)
             31 LOAD_FAST                1 (i)
             34 LOAD_CONST               2 (1)
             37 BINARY_ADD          
             38 CALL_FUNCTION            1
             41 POP_TOP             

  5          42 LOAD_FAST                1 (i)
             45 PRINT_ITEM          
             46 PRINT_NEWLINE       
             47 JUMP_ABSOLUTE           19
        >>   50 POP_BLOCK           
        >>   51 LOAD_CONST               0 (None)
             54 RETURN_VALUE        

In [6]: dis.dis(f2)
  2           0 LOAD_GLOBAL              0 (range)
              3 LOAD_CONST               1 (6)
              6 CALL_FUNCTION            1
              9 STORE_FAST               0 (it)

  3          12 SETUP_LOOP              29 (to 44)
             15 LOAD_FAST                0 (it)
             18 GET_ITER            
        >>   19 FOR_ITER                21 (to 43)
             22 STORE_FAST               1 (i)

  4          25 LOAD_FAST                0 (it)
             28 LOAD_CONST               2 (-2)
             31 SLICE+2             
             32 STORE_FAST               0 (it)

  5          35 LOAD_FAST                0 (it)
             38 PRINT_ITEM          
             39 PRINT_NEWLINE       
             40 JUMP_ABSOLUTE           19
        >>   43 POP_BLOCK           
        >>   44 LOAD_CONST               0 (None)

如您所见,for语句适用于itGET_ITER指令iter(it))的可迭代。因此,重新分配it变量不会影响循环迭代。

答案 2 :(得分:4)

首先,了解运行简单for循环时发生的事情是至关重要的,例如:

for i in it: pass

在循环开始时,会创建一个迭代器。该迭代器是对iter(it)的隐式调用的结果。这是时间,在上面的循环中引用了名为it的变量。其余的引用发生在迭代器上调用next时,但它使用迭代器保持引用的对象,而不是名称it绑定的对象。

这对你的第二个例子意味着什么?

请注意,在第二个示例中,您不会更改地址列表,而是创建新列表并将变量it绑定到该列表。

这意味着迭代器不断引用原始列表,该列表未更改。

在您的第一个示例中,您更改了原始列表,因此对next(iterator)的调用反映了这些更改。