深入理解 python 虚拟机:字节码教程——深入剖析循环实现原理

news2025/10/31 17:09:22

在本篇文章当中主要给大家介绍 cpython 当中跟循环相关的字节码,这部分字节码相比起其他字节码来说相对复杂一点,通过分析这部分字节码我们对程序的执行过程将会有更加深刻的理解。

循环

普通 for 循环实现原理

我们使用各种例子来理解和循环相关的字节码:

 
def test_loop():
for i in range(10):
print(i)

上面的代码对应的字节码如下所示:

 
8 0 LOAD_GLOBAL 0 (range)
2 LOAD_CONST 1 (10)
4 CALL_FUNCTION 1
6 GET_ITER
>> 8 FOR_ITER 12 (to 22)
10 STORE_FAST 0 (i)
9 12 LOAD_GLOBAL 1 (print)
14 LOAD_FAST 0 (i)
16 CALL_FUNCTION 1
18 POP_TOP
20 JUMP_ABSOLUTE 8
>> 22 LOAD_CONST 0 (None)
24 RETURN_VALUE

首先是 range 他对应一个 builtin 的类型,在执行上面的字节码的过程当中,首先先将 range 将在进入栈空间当中,然后将常量 10 加载进入栈空间当中,最后会调用指令 CALL_FUNCTION,这个时候会将栈顶的两个元素弹出,调用 range 类型的创建函数,这个函数会返回一个 range 的实例对象。

这个时候栈的结果如下所示:

接下来的一条字节码为 GET_ITER,这条字节码的含义为,弹出栈顶的对象,并且将弹出的对象变成一个迭代器,并且将得到的迭代器对象再压入栈空间当中。

接下来的一条指令是 FOR_ITER,这条指令的含义为:已知栈顶对象是一个迭代器,调用这个迭代器的 __next__ 函数 :

  • 如果迭代器已经迭代完成了,则将栈顶的迭代器弹出,并且将 bytecode 的 counter 加上对应的参数值,在上面的函数字节码当中这个参数值等于 12 ,也就是说下一条指令为字节码序列的 22 这个位置。
  • 如果没有迭代完成则将函数的返回值压入栈顶,并且不需要弹出迭代器,比如当我们第一次调用 __next__ 函数的时候,range 的返回值为0,那么此时栈空间的内容如下所示:

接下来执行的字节码为 STORE_FAST,这条字节码的含义就是弹出栈顶的元素,并且将这个元素保存到 co_varnames[var_num] 当中,var_num 就是这条字节码的参数,在上面的函数当中就是 0,对应的变量为 i ,因此这条字节码的含义就是弹出栈顶的元素并且保存到变量 i 当中。

LOAD_GLOBAL,将内嵌函数 print 加载进入栈中,LOAD_FAST 将变量 i 加载进入栈空间当中,此时栈空间的内容如下所示:

CALL_FUNCTION 会调用 print 函数打印数字 0,并且将函数的返回值压入栈空间当中,print 函数的返回值为 None,此时栈空间的内容如下所示:

POP_TOP 将栈顶的元素弹出,JUMP_ABSOLUTE 字节码有一个参数,在上面的函数当中这个参数为 8 ,当执行这条字节码的时候直接将 bytecode 的 counter 直接设置成这个参数值,因此执行完这条字节码之后下一条被执行的字节码又是 FOR_ITER,这便实现了循环的效果。

综合分析上面的分析过程,实现循环的效果主要是有两个字节码实现的,一个是 FOR_ITER,当迭代器迭代完成之后,会直接跳出循环,实现这个的原理是在字节码的 counter 上加上一个值,另外一个就是 JUMP_ABSOLUTE,他可以直接跳到某一处的字节码位置进行执行。

continue 关键字实现原理

 
def test_continue():
for i in range(10):
data = random.randint(0, 10)
if data < 5:
continue
print(f"{data = }")

其实通过对上面的字节码的分析之后,我们可以大致分析出 continue 的实现原理,首先我们知道 continue 的语意直接进行下一次循环,这个语意其实和循环体执行完之后的语意是一样的,在上一份代码的分析当中实现这个语意的字节码是 JUMP_ABSOLUTE,直接跳到 FOR_ITER 指令的位置继续开始执行。我们现在来看看上面的函数对应的字节码是什么:

 
13 0 LOAD_GLOBAL 0 (range)
2 LOAD_CONST 1 (10)
4 CALL_FUNCTION 1
6 GET_ITER
>> 8 FOR_ITER 40 (to 50)
10 STORE_FAST 0 (i)
14 12 LOAD_GLOBAL 1 (random)
14 LOAD_METHOD 2 (randint)
16 LOAD_CONST 2 (0)
18 LOAD_CONST 1 (10)
20 CALL_METHOD 2
22 STORE_FAST 1 (data)
15 24 LOAD_FAST 1 (data)
26 LOAD_CONST 3 (5)
28 COMPARE_OP 0 (<)
30 POP_JUMP_IF_FALSE 34
16 32 JUMP_ABSOLUTE 8
17 >> 34 LOAD_GLOBAL 3 (print)
36 LOAD_CONST 4 ('data = ')
38 LOAD_FAST 1 (data)
40 FORMAT_VALUE 2 (repr)
42 BUILD_STRING 2
44 CALL_FUNCTION 1
46 POP_TOP
48 JUMP_ABSOLUTE 8
>> 50 LOAD_CONST 0 (None)
52 RETURN_VALUE
  • LOAD_GLOBAL 0 (range): 加载全局变量 range,将其压入栈顶。
  • LOAD_CONST 1 (10): 加载常量值 10,将其压入栈顶。
  • CALL_FUNCTION 1: 调用栈顶的函数,此处为 range 函数,并传入一个参数,参数个数为 1。
  • GET_ITER: 获取迭代器对象。
  • FOR_ITER 40 (to 50): 迭代循环的开始,当迭代完成之后将字节码的 counter 加上 40 ,也就是跳转到 50 的位置执行。
  • STORE_FAST 0 (i): 将迭代器的值存储到局部变量 i 中。
  • LOAD_GLOBAL 1 (random): 加载全局变量 random,将其压入栈顶。
  • LOAD_METHOD 2 (randint): 加载对象 random 的属性 randint,将其压入栈顶。
  • LOAD_CONST 2 (0): 加载常量值 0,将其压入栈顶。
  • LOAD_CONST 1 (10): 加载常量值 10,将其压入栈顶。
  • CALL_METHOD 2: 调用栈顶的方法,此处为 random.randint 方法,并传入两个参数,参数个数为 2。
  • STORE_FAST 1 (data): 将方法返回值存储到局部变量 data 中。
  • LOAD_FAST 1 (data): 加载局部变量 data,将其压入栈顶。
  • LOAD_CONST 3 (5): 加载常量值 5,将其压入栈顶。
  • COMPARE_OP 0 (<): 执行比较操作 <,将结果压入栈顶。
  • POP_JUMP_IF_FALSE 34: 如果栈顶的比较结果为假,则跳转到字节码偏移为 34 的位置。
  • JUMP_ABSOLUTE 8: 无条件跳转到字节码偏移为 8 的位置,即循环的下一次迭代。
  • LOAD_GLOBAL 3 (print): 加载全局变量 print,将其压入栈顶。
  • LOAD_CONST 4 ('data = '): 加载常量字符串 'data = ',将其压入栈顶。
  • LOAD_FAST 1 (data): 加载局部变量 data,将其压入栈顶。
  • FORMAT_VALUE 2 (repr): 格式化栈顶的值,并指定格式化方式为 repr。
  • BUILD_STRING 2: 构建字符串对象,包含两个格式化后的值。
  • CALL_FUNCTION 1: 调用栈顶的函数,此处为 print 函数,并传入一个参数,参数个数为 1。
  • POP_TOP: 弹出栈顶的值,也就是函数 print 的返回值,print 函数返回值为 None 。
  • JUMP_ABSOLUTE 8: 无条件跳转到字节码偏移为 8 的位置,即循环的下一次迭代。
  • LOAD_CONST 0 (None): 加载常量值 None,将其压入栈顶。
  • RETURN_VALUE: 返回栈顶的值,即 None。

这段字节码实现了一个简单的循环,使用 range 函数生成一个迭代器,然后对迭代器进行遍历,每次遍历都会调用 random.randint 方法生成一个随机数并存储到局部变量 data 中,然后根据 data 的值进行条件判断,如果小于 5 则打印 "data = " 和 data 的值,否则继续下一次循环,直到迭代器结束。最后返回 None。

break 关键字实现原理

 
def test_break():
for i in range(10):
data = random.randint(0, 10)
if data < 5:
break
return "BREAK"

上面的函数对应的字节码如下所示:

 
21 0 LOAD_GLOBAL 0 (range)
2 LOAD_CONST 1 (10)
4 CALL_FUNCTION 1
6 GET_ITER
>> 8 FOR_ITER 28 (to 38)
10 STORE_FAST 0 (i)
22 12 LOAD_GLOBAL 1 (random)
14 LOAD_METHOD 2 (randint)
16 LOAD_CONST 2 (0)
18 LOAD_CONST 1 (10)
20 CALL_METHOD 2
22 STORE_FAST 1 (data)
23 24 LOAD_FAST 1 (data)
26 LOAD_CONST 3 (5)
28 COMPARE_OP 0 (<)
30 POP_JUMP_IF_FALSE 8
24 32 POP_TOP
34 JUMP_ABSOLUTE 38
36 JUMP_ABSOLUTE 8
26 >> 38 LOAD_CONST 4 ('BREAK')
40 RETURN_VALUE

这段字节码与之前的字节码相似,但有一些细微的不同。

  • LOAD_GLOBAL 0 (range): 加载全局变量 range,将其压入栈顶。
  • LOAD_CONST 1 (10): 加载常量值 10,将其压入栈顶。
  • CALL_FUNCTION 1: 调用函数,函数参数个数为 1。
  • GET_ITER: 从栈顶获取可迭代对象,并返回迭代器对象。
  • FOR_ITER 28 (to 38): 遍历迭代器,如果迭代器为空,则跳转到字节码偏移为 38 的位置,即跳出循环,否则继续执行下一条字节码。
  • STORE_FAST 0 (i): 将迭代器的当前值存储到局部变量 i 中。

接下来的字节码与之前的字节码相似,都是调用 random.randint 方法生成随机数,并将随机数存储到局部变量 data 中。然后,对局部变量 data 进行条件判断,如果小于 5 则跳出循环,否则继续下一次循环。不同的是,这里使用了 POP_TOP 操作来弹出栈顶的值,即格式化后的字符串,无需使用。

  • POP_JUMP_IF_FALSE 8: 如果栈顶的值(即 data)不满足条件(小于 5),则跳转到字节码偏移为 8 的位置,即循环的下一次迭代。
  • POP_TOP: 弹出栈顶的值,也就是将迭代器弹出。
  • JUMP_ABSOLUTE 38: 无条件跳转到字节码偏移为 38 的位置,即跳出循环。
  • JUMP_ABSOLUTE 8: 无条件跳转到字节码偏移为 8 的位置,即循环的下一次迭代。

最后,字节码加载了一个常量字符串 'BREAK',并通过 RETURN_VALUE 操作将其作为返回值返回。这段字节码实现了类似于之前的循环,但在满足条件时使用了 POP_TOP 和跳转指令来优化循环的执行。

从上面的分析过程可以看出来 break 的实现也是通过 JUMP_ABSOLUTE 来做到的,直接跳转到循环外部的下一行代码。

总结 

在本本篇文章当中主要给大家分析了在python当中也循环有关的字节码,实现循环操作的主要是几个核心的字节码 FOR_ITER ,JUMP_ABSOLUTE,GET_ITER 等等。只要深入了解了这几个字节码的功能理解循环的过程就很简单了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/863574.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

flutter 初识(开发体验,优缺点)

前言 最近有个跨平台桌面应用的需求&#xff0c;需要支持 windows/linux/mac 系统&#xff0c;要做个更新应用的小界面&#xff0c;主要功能就是下载更新文件并在本地进行替换&#xff0c;很简单的小功能。 花了几分钟构建没做 UI 优化的示例界面&#xff1a; 由于我们的客…

数据分析两件套ClickHouse+Metabase(二)

Metabase篇 Metabase安装部署 任何问题请查看 -> 官方文档 jar包从GitHub下载 -> 地址 同样有个问题, 默认数据源里没有ClickHouse, 不过ClickHouse官方提供了插件包 -> 插件包 在安装metabase目录下新建一个plugins文件夹, 把下载的clickhouse.metabase-driver.ja…

JavaSpring加载properties文件

手动加载 #properties文件 jdbc.driver1 <?xml version"1.0" encoding"UTF-8"?> <!-- 开启context命名空间--> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XM…

glove安装中的问题

万恶之源&#xff1a; >>> from glove import Glove Traceback (most recent call last):File "<stdin>", line 1, in <module>File "D:\code_related_software\Anaconda\lib\site-packages\glove\__init__.py", line 1, in <mod…

详解C语言函数:深入了解函数的使用和特性

目录 引言 一、函数的概念 1.1 函数关键特点 1.2 函数的组成部分 1.3 函数声明和定义格式 二、函数分类 2.1 库函数 使用库函数的步骤 2.2 自定义函数 创建自定义函数的步骤 三、函数的参数类型 3.1 形式参数&#xff08;形参&#xff09;&#xff1a; 格式&#x…

【碎碎念随笔】1、回顾我的电脑和编程经历

✏️ 闲着无事&#xff0c;讲述一下我的计算机和代码故事 一、初识计算机 &#x1f5a5;️ 余家贫&#xff0c;耕植无钱买电脑。大约六年级暑假&#xff0c;我在姐姐哪儿第一次接触到了计算机&#xff08;姐姐也是买的二手&#xff09;。 &#x1f5a5;️ 计算机真有趣&#x…

如何投诉删除360搜索下拉词?

有的企业发现自己品牌在360搜索下拉框里会展现出来一些负面词&#xff0c;如骗子、跑路、倒闭等&#xff0c;有企业咨询能不能删除360搜索下拉里的负面词&#xff1f;小马识途营销顾问分析要看具体情况&#xff0c;按经验如果是涉及诋毁诽谤的词&#xff0c;投诉到平台能够删除…

检测新突破 | AlignDet:支持各类检测器自监督新框架(ICCV2023)

引言 论文链接&#xff1a;https://arxiv.org/abs/2307.11077 项目地址&#xff1a;https://github.com/liming-ai/AlignDet 这篇论文主要研究目标检测领域的自监督预训练方法。作者首先指出&#xff0c;当前主流的预训练-微调框架在预训练和微调阶段存在数据、模型和任务上的…

Kafka的下载和安装

一、Kafka下载和安装 下载地址&#xff1a;https://kafka.apache.org/downloads 下载完毕解压即可 linux解压命令tar -zxvf kafka_2.13-3.5.1.tgz&#xff0c;linux环境下指令是在\kafka_2.13-3.5.1\bin目录。 windows直接解压即可&#xff0c;windows环境下指令是在kafka_2.…

centos7 安装 docker 不能看菜鸟教程的 docker 安装,有坑

特别注意 不能看菜鸟教程的 docker 安装&#xff0c;有坑 如果机器不能直接上网&#xff0c;先配置 yum 代理 proxyhttp://172.16.0.11:8443 配置文件修改后即刻生效&#xff0c;再执行 yum install 等命令&#xff0c;就可以正常安装软件了。 参考 https://blog.csdn.net/c…

倒计时 1 天!浙江大学、乘云数字、阿里云等多位技术专家教授解读最新运维技术

莫听监控繁杂声&#xff0c;何妨观测且徐行&#xff1b; 智能诊断快胜马&#xff0c;一键运维定乾坤。 各位朋友们&#xff0c;距离系统运维 MeetUp 开始只剩 2 天&#xff01;此次 MeetUp 将围绕 Linux 的应用和系统可观测&#xff0c;eBPF 跟踪以及诊断技术等系统运维热门话…

ApiPost的使用

1. 设计接口 请求参数的介绍 Query:相当于get请求&#xff0c;写的参数在地址栏中可以看到 Body: 相当于 post请求&#xff0c;请求参数不在地址栏中显示。 请求表单类型&#xff0c;用form-data json文件类型&#xff0c;用row 2. 预期响应期望 设置完每一项点一下生成响应…

C#质检工具(StyleCop、SonarLint)

1、StyleCop StyleCop工具主要类似java中的checkStyle,是检查代码样式规范的工具。 1.1、StyleCop安装流程: 图1.1 图1.2 图1.3 安装StyleCop插件时可能会遇到下载特慢或卡住不动的情况,需注意: 1)网上说的关闭IPV6功能不管用 2)网上说的自动指定dns不管用 3)网上…

ip地址怎么改 手机ip地址怎么修改

IP地址是指互联网协议地址&#xff0c;是给互联网上的每台设备分配的一个唯一的标识符。改变IP地址是在一定的条件下&#xff0c;为了实现一些特定的目的而对设备的网络配置进行调整。下面将介绍一些常见的改变IP地址的方法。我们可以通过重启路由器或者计算机来更改IP地址。在…

域名配置HTTPS

一、注册域名 这个可以在各大平台注册&#xff0c;具体看一下就会注册了&#xff0c;自己挑选一个自己喜欢的域名。 步骤一般也就是先实名&#xff0c;实名成功了才能注册域名。 二、办理SSL证书 这里使用的是阿里云的SSL免费证书 1、申请证书 二、填写申请 三、域名绑定生…

vue elementui v-for 循环el-table-column 第一列数据变到最后一个

这个动态渲染table表格时发现el-table-column 第一列数据变到最后一个 序号被排到后面 代码 修改后 <el-table:data"tableData"tooltip-effect"dark"style"width: 100%"height"500"><template v-for"(item, index) i…

【计算机网络】应用层协议Https

文章目录 前言Http加密方法对称加密非对称加密数据摘要&#xff08;数据指纹&#xff09;数字签名 加密方案只使用对称加密只使用非对称加密双方都使用非对称加密对称非对称 证书 前言 上一篇文章中&#xff0c;我们提到http是不安全的&#xff0c;尽管采用了cookie和session的…

数字电路 || 逻辑代数基础

逻辑函数和最小项 最大项和最小项 Tips&#xff1a;最小项的反是最大项&#xff0c;最大项的反是最小项。即&#xff0c; 计算题&#xff0c;分清与或式/或与式 转换关系 卡诺图 A是高位&#xff0c;BC是低位 在卡诺图上合并最小项&#xff1a;圈要少、圈最大&#xff0…

【word密码】Word编辑权限,如何设置?

Word文档可以设置加密来保护文件禁止修改&#xff0c;但是在word文档中设置限制编辑功能时对它的作用是否有详细的了解呢&#xff1f;今天为大家介绍word限制编辑功能的作用以及忘记了限制编辑密码该如何解决。 设置限制大家应该都清楚&#xff0c;就是点击工具栏中的审阅 – …

手机录音转文字让你工作更轻松

你是否经常需要把一段重要的录音转换成文字&#xff0c;但又不想手动去打字&#xff1f;现在&#xff0c;有许多手机软件可以帮助你实现这个目标&#xff0c;但面对如此多的选择&#xff0c;你可能会感到不知所措。不要担心&#xff0c;如果你还不知道录音转文字手机软件哪个好…