Gus*_*tav 78 python for-loop variable-assignment python-internals
这更像是一个概念性问题.我最近在Python中看到了一段代码(它在2.7中工作,也可能在2.5中运行),其中一个for循环对迭代的列表和列表中的项使用相同的名称,这既是一种糟糕的做法,也是一种根本不起作用的东西.
例如:
x = [1,2,3,4,5]
for x in x:
print x
print x
Run Code Online (Sandbox Code Playgroud)
产量:
1
2
3
4
5
5
Run Code Online (Sandbox Code Playgroud)
现在,我觉得打印的最后一个值是从循环中分配给x的最后一个值,但是我不明白为什么你能够为for循环的两个部分使用相同的变量名并且它按预期运作.它们在不同的范围内吗?引擎盖下发生了什么让这样的事情发挥作用?
Sea*_*ira 67
什么dis告诉我们:
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
Run Code Online (Sandbox Code Playgroud)
关键位是第2节和第3节 - 我们从x(24 LOAD_NAME 0 (x))中加载值,然后我们得到它的iterator(27 GET_ITER)并开始迭代它(28 FOR_ITER).Python 永远不会再回来加载迭代器了.
旁白:这将没有任何意义的话,因为它已经拥有了迭代器,并作为作者Abhijit在他的回答指出,Python的规范的第7.3实际上需要这种行为).
当名称x被覆盖以指向以前称为xPython 的列表中的每个值时,查找迭代器没有任何问题,因为它永远不需要x再次查看名称来完成迭代协议.
Abh*_*jit 42
使用示例代码作为核心参考
x = [1,2,3,4,5]
for x in x:
print x
print x
Run Code Online (Sandbox Code Playgroud)
摘录1
表达式列表评估一次; 它应该产生一个可迭代的对象.为expression_list的结果创建一个迭代器.
它的意思是,你的变量x,它是对象的符号名list:[1,2,3,4,5]被评估为一个迭代的对象.即使变量,符号引用改变了它的忠诚度,因为表达式列表不再被评估,所以对已经评估和生成的可迭代对象没有影响.
注意
摘录2
然后,对于迭代器提供的每个项,按升序索引的顺序执行一次该套件.
这里的套件引用迭代器而不是表达式列表.因此,对于每次迭代,执行迭代器以产生下一个项而不是引用原始表达式列表.
如果你考虑一下,它必须以这种方式工作.for循环序列的表达式可以是任何东西:
binaryfile = open("file", "rb")
for byte in binaryfile.read(5):
...
Run Code Online (Sandbox Code Playgroud)
我们无法在循环的每次传递中查询序列,或者在这里我们最终会在第二次从下一批5个字节中读取.当然,Python必须以某种方式在循环开始之前私下存储表达式的结果.
它们在不同的范围内吗?
不.要确认这一点,您可以保留对原始范围字典(locals())的引用,并注意到您实际上在循环中使用相同的变量:
x = [1,2,3,4,5]
loc = locals()
for x in x:
print locals() is loc # True
print loc["x"] # 1
break
Run Code Online (Sandbox Code Playgroud)
引擎盖下发生了什么让这样的事情发挥作用?
Sean Vieira准确地展示了幕后的内容,但是用更易读的python代码来描述它,你的for循环基本上等同于这个while循环:
it = iter(x)
while True:
try:
x = it.next()
except StopIteration:
break
print x
Run Code Online (Sandbox Code Playgroud)
这与您在旧版Java中看到的传统索引迭代方法不同,例如:
for (int index = 0; index < x.length; index++) {
x = x[index];
...
}
Run Code Online (Sandbox Code Playgroud)
当item变量和sequence变量相同时,此方法将失败,因为x在第一次x重新分配给第一个项目后,序列将不再可用于查找下一个索引.
但是,使用前一种方法,第一行(it = iter(x))请求一个迭代器对象,它实际上负责从那时开始提供下一个项目.x最初指向的序列不再需要直接访问.