首页 文章

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

提问于
浏览
77

这更像是一个概念性问题 . 我最近在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 循环的两个部分使用相同的变量名 . 让它按预期运作 . 它们在不同的范围内吗?引擎盖下发生了什么让这样的事情发挥作用?

6 回答

  • 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
    

    关键位是第2和第3部分 - 我们从 x24 LOAD_NAME 0 (x) )中加载该值,然后我们得到它的迭代器( 27 GET_ITER )并开始迭代它( 28 FOR_ITER ) . Python永远不会再回来加载迭代器了 .

    旁白:这样做没有任何意义,因为它已经有了迭代器,并且Abhijit points out in his answerSection 7.3 of Python's specification实际上需要这种行为) .

    当名称_562504被覆盖以指向以前称为_562505的列表中的每个值时,Python在查找迭代器时没有任何问题,因为它永远不需要再次查看名称 x 来完成迭代协议 .

  • 5

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

    x = [1,2,3,4,5]
    for x in x:
        print x
    print x
    

    我希望您参考手册中的7.3. The for statement部分

    Excerpt 1

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

    这意味着您的变量 x ,它是对象 list[1,2,3,4,5] 的符号名称,被计算为可迭代对象 . 即使变量,符号引用改变其效忠,因为expression-list不再被评估,对已经评估和生成的可迭代对象没有影响 .

    Note

    • Python中的所有内容都是一个Object,具有标识符,属性和方法 .

    • 变量是符号名称,是对任何给定实例的一个且仅一个对象的引用 .

    • 运行时的变量可以改变其忠诚度,即可以引用其他一些对象 .

    Excerpt 2

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

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

  • 1

    如果你考虑一下,它必须以这种方式工作 . for 循环序列的表达式可以是任何东西:

    binaryfile = open("file", "rb")
    for byte in binaryfile.read(5):
        ...
    

    我们可以't query the sequence on each pass through the loop, or here we'd第二次从下一批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
    

    幕后发生了什么让这样的事情发挥作用?

    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];
        ...
     }
    

    当item变量和sequence变量相同时,此方法将失败,因为在第一次 x 被重新分配给第一个项目之后,序列 x 将不再可用于查找下一个索引 .

    但是,使用前一种方法,第一行( it = iter(x) )请求iterator object,这实际上是负责从那时开始提供下一个项目 . 不再需要直接访问 x 最初指向的序列 .

  • 3

    x 不再引用原始 x 列表,因此's no confusion. Basically, python remembers it'迭代原始 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
    
  • 4

    它是变量(x)和它指向的对象(列表)之间的差异 . 当for循环开始时,Python会抓取对x指向的对象的内部引用 . 它使用对象而不是x在任何给定时间引用的内容 .

    如果重新分配x,则for循环不会更改 . 如果x指向可变对象(例如,列表)并且您更改该对象(例如,删除元素),则结果可以是不可预知的 .

  • 42

    基本上,for循环接受列表 x ,然后将其存储为临时变量, re 为该临时变量中的每个值分配 x . 因此, 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 作为 bar 存储在 foo() 中,因此虽然 x 正在重新分配,但它仍然存在于 foo() 中,因此我们可以使用它来触发 for 循环 .

相关问题