Python for循环实际上是如何工作的?

时间:2019-01-27 12:00:08

标签: python for-loop python-internals

我很好奇理解Python for循环是如何在后台运行的。我试着像下面的代码片段那样实现它,就是for循环是如何实现的?

my_list = [1, 2, 3, 4, 5]

# list itself is iterable but not iterator. Make it an iterator
iter_list = iter(my_list)

while True:
    try:
       print(next(iter_list))
    except StopIteration:
       break

1 个答案:

答案 0 :(得分:3)

是的,这与for循环构造的实现方式非常相似。它肯定与for loop statement documentation相匹配:

  

表达式列表只计算一次;它应该产生一个可迭代的对象。为expression_list的结果创建一个迭代器。然后,按照迭代器返回的顺序,对迭代器提供的每个项目执行一次套件。依次使用分配的标准规则将每个项目分配给目标列表(请参见分配语句),然后执行套件。当项目用尽时(紧接序列为空或迭代器引发StopIteration异常时),将执行else子句中的套件(如果存在),并且循环终止。 / p>

您只错过了使用标准分配规则分配给目标列表的 部分;您将不得不使用i = next(iter_list)print(i)而不是直接打印next()调用的结果。

Python源代码被编译为 bytecode ,然后由解释器循环执行。您可以使用dis module查看for循环的字节码:

>>> import dis
>>> dis.dis('for i in mylist: pass')
  1           0 SETUP_LOOP              12 (to 14)
              2 LOAD_NAME                0 (mylist)
              4 GET_ITER
        >>    6 FOR_ITER                 4 (to 12)
              8 STORE_NAME               1 (i)
             10 JUMP_ABSOLUTE            6
        >>   12 POP_BLOCK
        >>   14 LOAD_CONST               0 (None)
             16 RETURN_VALUE

在同一个dis模块中记录了各种命名的操作码,它们的实现可以在CPython evaluation loop中找到(寻找TARGET(<opcode>)切换目标);上面的操作码分解为:

  • SETUP_LOOP 12标志着 suite 的开始,这是一条语句块,因此解释器知道在发生break的情况下跳转到哪里,以及需要进行哪些清除工作在出现异常或return语句的情况下完成;清理操作码位于此操作码之后的字节码的12个字节中(因此此处为POP_BLOCK)。
  • LOAD_NAME 0 (mylist)加载mylist变量值,并将其放在堆栈顶部(操作码描述中的 TOS )。
  • GET_ITER对TOS上的对象调用iter(),然后用结果替换TOS。
  • FOR_ITER 4在TOS迭代器上调用next()。如果给出结果,则将其推送到TOS。如果存在StopIteration异常,则将迭代器从TOS中删除,并将4个字节的字节码跳过到POP_BLOCK操作码中。
  • STORE_NAME 1提取TOS并将其放入命名变量中,此处为i
  • JUMP_ABSOLUTE 6标记循环主体的结尾;它告诉解释器返回到上面的FOR_ITER指令的字节码偏移量6。如果我们在循环中做了一些有趣的事情,那将发生在STORE_NAME之后,JUMP_ABSOLUTE之前。
  • POP_BLOCK删除了SETUP_LOOP设置的阻止簿记,并从堆栈中删除了迭代器。

>>标记是跳转目标,它们是视觉提示,可以方便地在读取跳转到它们的操作码行时发现它们。