[oeasy]python0010 - python虚拟机解释执行py文件的原理
解释运行程序 🥊
回忆上次内容
我们这次设置了断点
设置断点的目的是更快地调试
调试的目的是去除
bug
别害怕
bug
一步步地总能找到
bug
这就是程序员基本功
调试
debug
我心中还是有疑问
python3
是怎么解释hello.py
的?🤔
纯文本
我们的 py 文件是一个纯文本文件

打开我们的 guido.py
如果没有就新做一个
这里面是一个个的字符
python 怎知道如何执行呢?
传统文本
传统文本的基础也是字符

在字符的基础上组织起篇章结构
字组成词
词组成句
句组成段
段组成章节
最后成书
tokenize
首先把一个个字符组成词
分析一下哪些字可以组成词
术语叫词法分析 (lexical analysis)

把原来的字符流
变成了词的流
token (令牌) 流

词法分析之后输出的是一个词 (token) 的流
啥是 token 呢?
token
token
令牌

古人说听我号令
号指的是
号角
摔杯为号
是一个信号
令指的是令牌
急急如律令
打五十大板
令行禁止
怎么把源文件变成一个词 (token) 流呢?
python3 模块
帮助手册里面有这个内容
这个 tokenize 是 python3 的一个模块 (module)

具体怎么运行呢?
token 流

我们尝试运行
python3 -m tokenizeguido.py
-m 代表的是 module 模块
对 guido.py 进行词法分析
分析出来的词 (token) 流什么样子呢?
这个词的流怎么理解呢?
token 流
第 0 行设置了编码格式
第 1 行 [0,5) 字符是第 1 行第 1 个 token
print
print 是一个 Name (名字)
第 1 行 [5,6) 字符是第 1 行第 2 个 token
(
(是一个 Operator (操作符)
第 1 行 [6,30) 字符是第 1 行第 3 个 token
"1982------Guido in cwi"
这是一个 String (字符串)
第 1 行 [30,31) 字符是第 1 行第 4 个 token
)
) 是一个 Operator (操作符)
第 1 行 [31,32) 字符是第 1 行第 5 个 token
\n
\n 是一个 NewLine (换行符)
换行符意味着第一行结束

第 2 行...
词分析出来之后呢?
组词
词分析出来就是怎么组词的问题
哪些词和哪些词先组合
哪些词和哪些词后组合
生成一棵抽象语法树
AST(Abstract Syntax Tree)

我能看看这棵 ast 树么?
引入 ast 模块

具体怎么做呢?
流程
先把这个 ast 模块导入 (import) 进来
第一句就是 import ast
回车之后没有任何报错
那就是执行成功了
后面也一样
没有报错就是执行成功了

然后读取 guido.py 并送到 s
然后对于 s 进行语法分析 (parse)

再把分析 (parse) 的结果进行转储 (dump)
看起来有点乱
可以清晰一些么?
升级 Python
目前 lanqiao.cn 上面的 python 是 3.8
这个清晰缩进的格式需要在 3.9 以上完成
需要升级
sudo apt updatesudo apt install python3.9
升级之后就可以使用 Python3.9 了

缩进换行
只能在本地演示一下

这个就是把词组成语法树的样子
如何理解这棵树呢?
我们看一个例子
表达式运算
如果给的表达式为 1 * 2 * 3

结合序为下图

前两个先结合
得到的结果作为下一个运算的左操作数
然后和第 3 个结合
结合序
如果把 第一个 * 改成 + 号
其他什么也没加

表达式是 1 + 2 * 3

后两个会先结合
得到的结果作为下一个运算的右操作数
然后再和 1 进行加法运算
有了语法树
下一步要做什么呢?
这棵语法树我们能看懂
但是 cpu 需要的是能执行的一条条字节码指令
翻译成字节码
要把源程序翻译成字节码才能执行
字节码对应着 cpu 的指令
怎么把 ast 转化为字节码 (指令) 呢?
需要编译 (compile)
从一种语言到另一种语言
从 py 文件
到字节码 (指令)
就是编译

我可以看看这个编译过程么?
compile

编译结果
编译 (compile) 之后得到是字节码指令文件
所以扩展名是 pyc
其中 c 代表 compiled
pyc 是字节码 (bytecode) 文件
python 虚拟机的虚拟 cpu 就可以直接执行了
先看看这个 pyc 文件
注意他在
__pycache__
文件夹下cache 的意思是缓存
pycache 两端各有 2 条下划线 (_)
进这个文件夹看看
进入__pycache__
文件夹
打开 pyc 文件
得到的字节码看起来完全是乱码
可以想办法看懂这些字节码么?
vi 打开这个这个 pyc 文件
二进制形态
:set wrap 设置换行
这样看到了他的字符串形态
可以看到他的二进制字节形态么?
机器语言
:%!xxd
把文件转化为字节形态
这纯纯的机器语言字节形态
实在是看不懂啊😭
这真的是指令么?
究竟什么是指令呢?
指令
instruction
最早指的是教的行为或者过程
计算机领域里面特指指令
比如加法指令
减法指令
可以让 cpu 做特定运算的指令
由于计算机只认识 0 和 1
所以要把这些加加减减的指令
对应到 0 和 1 的二进制形态上去
0 和 1 的二进制形态我们记不住
于是有了汇编助记符
助记符告诉我们这条 0 和 1 的二进制形态
到底对应什么指令
助记符的语言就是汇编语言
汇编 assemble
assemble 指的是收集、集结
assembler 指的是装卸工
在计算机中特指汇编语言
可以让我们把 0 和 1 的机器指令
收集起来形成的助记符集合
就是汇编语言指令集
这就是汇编语言和 0101 的对应关系
反编译
disassemble
这个词由两部分组成
dis (反着来的)
dislike
disgrace
disagree
assembler (汇编语言)
disassemble 反编译
把 py 源文件编译成的字节码 (指令) 我们人类看不明白
把这些字节码 (指令) 反编译 (disassemble) 成汇编语言助记符
有了助记符我们就知道指令的含义了
这可以用么?
去试试!
反编译 (dis)
python3 -m disguido.py
-m 代表使用模块
dis 代表反编译 (disassemble)
我们可以看见
前面是行号
每行对应 4 条指令
LOAD_NAME 装载 (函数) 名字
LOAD_CONST 装载常量
CALL_FUNCTION 调用函数
POP_TOP 弹栈
总共 6 句
对应 6 组字节码
每组两个字节
那具体这个 LOAD_NAME 是要做些什么呢?
指令
LOAD_NAME
把一个值压入堆栈 co_names
把 print 这个函数名压入了堆栈
一会儿就要调用这个被压入堆栈的 print 函数
但是 LOAD_NAME 这条指令
具体对应什么二进制字节状态呢?
这个去哪里找呢?
python 源头
python 是从哪里来的呢?
python 是开源编程语言
整个的源代码都是开放的
我们可以去 github 找到他的源代码
https://github.com/python/cpython
二进制状态
搜索 LOAD_NAME 并且排查
找到字节码状态位置
指令对应着一个字节码状态值
https://github.com/python/cpython/blob/main/Lib/opcode.py
这样我们能否找到
4 条指令分别对应的字节状态值
4 条指令
指令助记符
指令含义
十进制状态
十六进制状态
LOAD_NAME
装载函数名称
101
0x65
LOAD_CONST
装载参数
100
0x64
CALL_FUNCTION
调用函数
142
0x8e
POP_TOP
弹栈返回
1
0x01
可以找到源代码的对应关系么?

好像找到了
64XX
64 00 是从表中的 00 号位置取得字符串 "Guido in cwi"
64 01 是从表中的 01 号位置取字符串 "Guido in cnri"
...
以此类推,直到 05 83 取出字符串 "Guido in microsoft"
0x83 对应的是 GET_AWAITABLE
可等待地调用
那这些二进制代码究竟是什么指令集的呢?
首先我们得弄懂什么是指令集呢?
指令集
指令集 就是
指令的集合

上图是 arm 的指令集
也常被称作 arm 架构
那什么又是架构呢?
architect
architect 原本的英文含义是
建筑

architecture
造房子的人
就是建筑师

在 cpu 领域
architect
architecture
指的是什么呢?
架构师

架构师
软件开发行业从业者的终极形态
非常硬核的存在

那 python 的字节码用的是什么架构呢?
arm
还是 x86 呢?
虚拟机的虚拟 cpu
pyc 的这些字节码 (bytecode)
对应的是 python 虚拟机上面虚拟 cpu 的指令集

cpu 也能虚拟吗?
我们先把这节课总结一下
总结
我们把 python 源文件
词法分析 得到 词流 (token stream)
语法分析 得到 抽象语法树 (Abstract Syntax Tree)
编译 得到 字节码 (bytecode)
字节码我们看不懂
所以反编译 得到 指令文件 (opcode)

指令文件是基于 python 虚拟机的虚拟 cpu 的指令集
什么是 python 虚拟机呢?🤔
我们下次再说👋
蓝桥->https://www.lanqiao.cn/teacher/3584
github->https://github.com/overmind1980/oeasy-python-tutorial
gitee->https://gitee.com/overmind1980/oeasypython
视频->https://www.bilibili.com/video/BV1CU4y1Z7gQ 作者:oeasy 作者:oeasy https://www.bilibili.com/read/cv19181659 出处:bilibili