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

问题描述 投票:0回答:2

我很想了解 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
python for-loop python-internals
2个回答
9
投票

是的,这是

for
循环构造的实现方式的一个很好的近似。它当然符合
for
循环语句文档
:

表达式列表被评估一次;它应该产生一个可迭代的对象。为

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

您只错过了使用标准分配规则分配到目标列表部分;您必须使用

i = next(iter_list)
print(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

命名的各种操作码记录在同一个

dis
模块中,并且它们的实现可以在 CPython 评估循环中找到(查找
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
    标记循环体的结束;它告诉解释器返回到字节码偏移量 6,即上面的
    FOR_ITER
    指令。如果我们在循环中做了一些有趣的事情,那么这将发生在
    STORE_NAME
    之后、
    JUMP_ABSOLUTE
    之前。
  • POP_BLOCK
    删除由
    SETUP_LOOP
    设置的块簿记,并从堆栈中删除迭代器。

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


0
投票

作为 Martijn 已经说过的话的补充,在不使用

for
的情况下,最接近在 Python 中实现的
for
循环的方法是转换:

for x in mylist:
    print(x)

对此:

NULL = object()  # We don't have real NULL from C, but we can simulate it as a guaranteed unique object; avoids invoking exception machinery in common case
iter_obj = iter(iterable)
while (x := next(iter_obj, NULL)) is not NULL:
    print(x)

与你猜测的主要区别是:

  1. 如果
    next
    调用、名称赋值和循环结束检查之外的任何内容都不会包含在
    try
    块中(如果其他任何内容引发
    StopIteration
    ,则它不会被
    for 捕获) 
    循环机械)
  2. No
    break
    用于正常循环退出条件(如果
    else:
    块附加到
    for
    ,则很重要;如果你
    break
    ,则跳过它,如果正常退出,则调用它,两者都是真实的)
    for
    循环和这个
    while
    模拟)
© www.soinside.com 2019 - 2024. All rights reserved.