【问题标题】:Changing iterable variable during loop在循环期间更改可迭代变量
【发布时间】:2016-07-29 12:09:32
【问题描述】:

it 成为python 中的可迭代元素。 在什么情况下,it 的变化会在 it 上的循环内反映出来?或更直截了当:这样的事情什么时候起作用?

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

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

另一方面是

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

导致输出:

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

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

澄清:

一个例子,这是行不通的:

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

导致打印“None”并抛出错误(NoneType 没有属性“remove”)。

【问题讨论】:

标签: python for-loop


【解决方案1】:

当您迭代list 时,您实际上调用了list.__iter__(),它返回绑定到listlistiterator 对象,然后实际迭代此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 不会影响 listiterator (它保留它自己对列表的引用),但 mutating itt 显然会影响它(因为两个引用都指向同一个列表)。

IOW 这是重新绑定和变异之间的旧区别......如果没有 for 循环,您将获得相同的行为:

# creates a `list` and binds it to name "a"
a = [1, 2, 3] 
# get the object bound to name "a" and binds it to name "b" too.
# at this point "a" and "b" both refer to the same `list` instance
b = a 
print id(a), id(b)
print a is b
# so if we mutate "a" - actually "mutate the object bound to name 'a'" - 
# we can see the effect using any name refering to this object:
a.append(42)
print b

# now we rebind "a" - make it refer to another object
a = ["a", "b", "c"]
# at this point, "b" still refer to the first list, and
# "a" refers to the new ["a", "b", "c"] list
print id(a), id(b)
print a is b
# and of course if we now mutate "a", it won't reflect on "b"
a.pop()
print a
print b

【讨论】:

  • 你能再扩展一点吗?是否还有其他对象,情况相同?
  • @Dschoni 取决于您所说的“相同情况”... wrt/“变异与重新绑定”部分,这就是 Python 的工作原理——“变量”不是“内存桶”,只是引用对象的名称。如果您想了解有关迭代器协议的更多信息,它在 Python 的 FineManual 中有完整的文档,但您可以在此处获得介绍和更多参考资料:stackoverflow.com/questions/9884132/…
【解决方案2】:

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

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

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 语句与 it 的可迭代对象(GET_ITER 指令,iter(it))一起使用。因此,重新分配it 变量不会影响循环迭代。

【讨论】:

  • 您对dis 的使用表示赞同,因为我以前从未见过它,而且看起来非常棒。我要亲自检查我的清关案例。
  • 这就是我喜欢 Python 的原因——它提供了很多开箱即用的神奇东西
【解决方案3】:

首先,必须了解运行一个简单的 for 循环时的幕后情况,例如:

for i in it: pass

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

这对您的第二个示例意味着什么?

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

表示迭代器一直在引用原始列表,没有改变。

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

【讨论】:

    猜你喜欢
    • 2016-05-23
    • 2022-01-14
    • 2014-11-09
    • 2011-01-04
    • 1970-01-01
    • 2013-05-15
    • 1970-01-01
    • 1970-01-01
    • 2015-04-08
    相关资源
    最近更新 更多