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

6 python for-loop python-internals

我很想了解 Pythonfor循环在幕后是如何工作的。我尝试像下面的代码片段一样实现它,这就是 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
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 9

是的,这是for循环构造如何实现的一个很好的近似。它当然与for循环语句文档匹配:

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

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

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

命名的各种操作码记录在同一模块中,并且可以在CPython 评估循环dis中找到它们的实现(查找switch 目标);上面的操作码分解为:TARGET(<opcode>)

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

标记>>是跳转目标,作为视觉提示,可以在阅读跳转到它们的操作码行时更容易发现它们。