为什么我可以在Python for循环中为迭代器和序列使用相同的名称?
这更多的是一个概念性的问题。 我最近在Python中看到了一段代码(它在2.7中工作,也可能在2.5中运行),其中for
循环对迭代的列表使用相同的名称,这个名单让我觉得既是不好的做法,又是一种根本不应该做的事情。
例如:
x = [1,2,3,4,5] for x in x: print x print x
产量:
1 2 3 4 5 5
现在,对我来说最后打印的值将是从循环中分配给x的最后一个值,但是我不明白为什么你可以在for
循环的两个部分中使用相同的variables名,有它的function如预期。 他们在不同的范围? 发生了什么事情可以让这样的工作?
什么不告诉我们:
Python 3.4.1 (default, May 19 2014, 13:10:29) [GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> from dis import dis >>> dis("""x = [1,2,3,4,5] ... for x in x: ... print(x) ... print(x)""") 1 0 LOAD_CONST 0 (1) 3 LOAD_CONST 1 (2) 6 LOAD_CONST 2 (3) 9 LOAD_CONST 3 (4) 12 LOAD_CONST 4 (5) 15 BUILD_LIST 5 18 STORE_NAME 0 (x) 2 21 SETUP_LOOP 24 (to 48) 24 LOAD_NAME 0 (x) 27 GET_ITER >> 28 FOR_ITER 16 (to 47) 31 STORE_NAME 0 (x) 3 34 LOAD_NAME 1 (print) 37 LOAD_NAME 0 (x) 40 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 43 POP_TOP 44 JUMP_ABSOLUTE 28 >> 47 POP_BLOCK 4 >> 48 LOAD_NAME 1 (print) 51 LOAD_NAME 0 (x) 54 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 57 POP_TOP 58 LOAD_CONST 5 (None) 61 RETURN_VALUE
关键位是第2和第3部分 – 我们从x
( 24 LOAD_NAME 0 (x)
)中加载值,然后我们得到它的迭代器( 27 GET_ITER
)并开始遍历它( 28 FOR_ITER
)。 Python 永远不会再次加载迭代器 。
除此之外:这样做没有任何意义,因为它已经有了迭代器,正如Abhijit在他的回答中指出的 ,Python规范的第7.3节实际上需要这种行为)。
当名称x
被覆盖,指向列表中的每个值(以前称为x
Python在查找迭代器时没有任何问题,因为它不需要再次查看名称x
来完成迭代协议。
使用您的示例代码作为核心参考
x = [1,2,3,4,5] for x in x: print x print x
我希望你参考7.3节。 手册中的for语句
摘录1
expression式列表被评估一次; 它应该产生一个可迭代的对象。 为expression_list的结果创build一个迭代器。
这意味着你的variablesx
是一个对象list
的符号名: [1,2,3,4,5]
被计算为一个可迭代的对象。 即使variables,符号引用改变其效忠,因为expression式列表不再被评估,对已经被评估和生成的可迭代对象没有影响。
注意
- Python中的所有东西都是一个对象,有一个标识符,属性和方法。
- variables是符号名称,对任何给定实例中的唯一对象的引用。
- 运行时的variables可以改变它的忠诚,即可以引用其他一些对象。
节选2
然后,该套件按迭代器提供的每个项目按升序索引的顺序执行一次。
这里的套件是指迭代器而不是expression式列表。 所以,对于每次迭代,迭代器都会被执行,以产生下一个项目,而不是引用原始expression式列表。
如果你仔细想想,这是必要的。 for
循环的expression式可以是任何东西:
binaryfile = open("file", "rb") for byte in binaryfile.read(5): ...
我们不能通过循环查询每个循环的序列,或者在这里我们最终第二次读取下一批的5个字节。 自然地,Python必须以某种方式在循环开始之前私下存储expression式的结果。
他们在不同的范围?
不,要确认这一点,你可以保留对原始作用域字典( locals() )的引用,并注意到你实际上在循环中使用了相同的variables:
x = [1,2,3,4,5] loc = locals() for x in x: print locals() is loc # True print loc["x"] # 1 break
发生了什么事情可以让这样的工作?
肖恩·维埃拉(Sean Vieira)精确地展示了底层的情况,但是要用更可读的Python代码来描述它,你的for
循环基本上等于这个while
循环:
it = iter(x) while True: try: x = it.next() except StopIteration: break print x
这与传统的迭代方法有所不同,您可以在老版本的Java中看到这种迭代方法,例如:
for (int index = 0; index < x.length; index++) { x = x[index]; ... }
当项variables和序列variables相同时,这种方法会失败,因为在第一次将x
重新分配给第一项之后,序列x
将不再可用于查找下一个索引。
然而,对于前一种方法,第一行( it = iter(x)
)会请求一个迭代器对象 ,该对象实际上负责提供下一个项目。 x
原来指向的序列不再需要直接访问。
它是variables(x)和它指向的对象(列表)之间的区别。 当for循环开始时,Python抓取x指向的对象的内部引用。 它使用该对象,而不是在任何给定时间x碰巧引用的对象。
如果您重新分配x,则for循环不会更改。 如果x指向一个可变对象(例如,一个列表),并且你改变该对象(例如,删除一个元素),结果可能是不可预知的。
基本上,for循环接受列表x
,然后将其存储为一个临时variables,然后将x
赋值给该临时variables中的每个值。 因此, x
现在是列表中的最后一个值。
>>> x = [1, 2, 3] >>> [x for x in x] [1, 2, 3] >>> x 3 >>>
就像这样:
>>> def foo(bar): ... return bar ... >>> x = [1, 2, 3] >>> for x in foo(x): ... print x ... 1 2 3 >>>
在这个例子中, x
以foo()
forms存储,所以虽然x
被重新分配,但它仍然存在(ed)在foo()
以便我们可以使用它来触发我们的for
循环。
x
不再是指原来的x
列表,所以没有混淆。 基本上,python记得它正在迭代原来的x
列表,但只要你开始将迭代值(0,1,2,等)分配给名称x
,它不再引用原来的x
列表。 名称被重新分配到迭代值。
In [1]: x = range(5) In [2]: x Out[2]: [0, 1, 2, 3, 4] In [3]: id(x) Out[3]: 4371091680 In [4]: for x in x: ...: print id(x), x ...: 140470424504688 0 140470424504664 1 140470424504640 2 140470424504616 3 140470424504592 4 In [5]: id(x) Out[5]: 140470424504592