我很好奇理解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
答案 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
设置的阻止簿记,并从堆栈中删除了迭代器。 >>
标记是跳转目标,它们是视觉提示,可以方便地在读取跳转到它们的操作码行时发现它们。