为什么我可以在Python for循环中为迭代器和序列使用相同的名称?

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再次查看名称来完成迭代协议.

  • "Python永远不会再回来加载迭代器了(这样做没有任何意义,因为它已经有了迭代器)." 这描述了您在反汇编中观察到的行为,但没有说明*是否*具有*; [Abhijit的回答](http://stackoverflow.com/a/24690950/1281433)引用了实际指定的手册. (8认同)

Abh*_*jit 42

使用示例代码作为核心参考

x = [1,2,3,4,5]
for x in x:
    print x
print x
Run Code Online (Sandbox Code Playgroud)

我希望你参考7.3.手册中的for语句

摘录1

表达式列表评估一次; 它应该产生一个可迭代的对象.为expression_list的结果创建一个迭代器.

它的意思是,你的变量x,它是对象的符号名list:[1,2,3,4,5]被评估为一个迭代的对象.即使变量,符号引用改变了它的忠诚度,因为表达式列表不再被评估,所以对已经评估和生成的可迭代对象没有影响.

注意

  • Python中的所有内容都是Object,具有标识符,属性和方法.
  • 变量是符号名称,在任何给定实例中对一个且仅一个对象的引用.
  • 运行时的变量可以改变其忠诚度,即可以引用其他一些对象.

摘录2

然后,对于迭代器提供的每个项,按升序索引的顺序执行一次该套件.

这里的套件引用迭代器而不是表达式列表.因此,对于每次迭代,执行迭代器以产生下一个项而不是引用原始表达式列表.


nmc*_*ean 5

如果你考虑一下,它必须以这种方式工作.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最初指向的序列不再需要直接访问.