[oeasy]python0010 - python虛擬機(jī)解釋執(zhí)行py文件的原理
?
解釋運(yùn)行程序 ??
回憶上次內(nèi)容
我們這次設(shè)置了斷點(diǎn)
設(shè)置斷點(diǎn)的目的是更快地調(diào)試
調(diào)試的目的是去除
bug
別害怕
bug
一步步地總能找到
bug
這就是程序員基本功
調(diào)試
debug
我心中還是有疑問
python3
是怎么解釋hello.py
的???
純文本
我們的 py 文件是一個(gè)純文本文件

打開我們的 guido.py
如果沒有就新做一個(gè)
這里面是一個(gè)個(gè)的字符
python 怎知道如何執(zhí)行呢?
傳統(tǒng)文本
傳統(tǒng)文本的基礎(chǔ)也是字符

在字符的基礎(chǔ)上組織起篇章結(jié)構(gòu)
字組成詞
詞組成句
句組成段
段組成章節(jié)
最后成書
tokenize
首先把一個(gè)個(gè)字符組成詞
分析一下哪些字可以組成詞
術(shù)語叫詞法分析 (lexical analysis)

把原來的字符流
變成了詞的流
token (令牌) 流

詞法分析之后輸出的是一個(gè)詞 (token) 的流
啥是 token 呢?
token
token
令牌

古人說聽我號(hào)令
號(hào)指的是
號(hào)角
摔杯為號(hào)
是一個(gè)信號(hào)
令指的是令牌
急急如律令
打五十大板
令行禁止
怎么把源文件變成一個(gè)詞 (token) 流呢?
python3 模塊
幫助手冊(cè)里面有這個(gè)內(nèi)容
這個(gè) tokenize 是 python3 的一個(gè)模塊 (module)

具體怎么運(yùn)行呢?
token 流

我們嘗試運(yùn)行
python3 -m tokenizeguido.py
-m 代表的是 module 模塊
對(duì) guido.py 進(jìn)行詞法分析
分析出來的詞 (token) 流什么樣子呢?
這個(gè)詞的流怎么理解呢?
token 流
第 0 行設(shè)置了編碼格式
第 1 行 [0,5) 字符是第 1 行第 1 個(gè) token
print
print 是一個(gè) Name (名字)
第 1 行 [5,6) 字符是第 1 行第 2 個(gè) token
(
(是一個(gè) Operator (操作符)
第 1 行 [6,30) 字符是第 1 行第 3 個(gè) token
"1982------Guido in cwi"
這是一個(gè) String (字符串)
第 1 行 [30,31) 字符是第 1 行第 4 個(gè) token
)
) 是一個(gè) Operator (操作符)
第 1 行 [31,32) 字符是第 1 行第 5 個(gè) token
\n
\n 是一個(gè) NewLine (換行符)
換行符意味著第一行結(jié)束

第 2 行...
詞分析出來之后呢?
組詞
詞分析出來就是怎么組詞的問題
哪些詞和哪些詞先組合
哪些詞和哪些詞后組合
生成一棵抽象語法樹
AST(Abstract Syntax Tree)

我能看看這棵 ast 樹么?
引入 ast 模塊

具體怎么做呢?
流程
先把這個(gè) ast 模塊導(dǎo)入 (import) 進(jìn)來
第一句就是 import ast
回車之后沒有任何報(bào)錯(cuò)
那就是執(zhí)行成功了
后面也一樣
沒有報(bào)錯(cuò)就是執(zhí)行成功了

然后讀取 guido.py 并送到 s
然后對(duì)于 s 進(jìn)行語法分析 (parse)

再把分析 (parse) 的結(jié)果進(jìn)行轉(zhuǎn)儲(chǔ) (dump)
看起來有點(diǎn)亂
可以清晰一些么?
升級(jí) Python
目前 lanqiao.cn 上面的 python 是 3.8
這個(gè)清晰縮進(jìn)的格式需要在 3.9 以上完成
需要升級(jí)
sudo apt updatesudo apt install python3.9
升級(jí)之后就可以使用 Python3.9 了

縮進(jìn)換行
只能在本地演示一下

這個(gè)就是把詞組成語法樹的樣子
如何理解這棵樹呢?
我們看一個(gè)例子
表達(dá)式運(yùn)算
如果給的表達(dá)式為 1 * 2 * 3

結(jié)合序?yàn)橄聢D

前兩個(gè)先結(jié)合
得到的結(jié)果作為下一個(gè)運(yùn)算的左操作數(shù)
然后和第 3 個(gè)結(jié)合
結(jié)合序
如果把 第一個(gè) * 改成 + 號(hào)
其他什么也沒加

表達(dá)式是 1 + 2 * 3

后兩個(gè)會(huì)先結(jié)合
得到的結(jié)果作為下一個(gè)運(yùn)算的右操作數(shù)
然后再和 1 進(jìn)行加法運(yùn)算
有了語法樹
下一步要做什么呢?
這棵語法樹我們能看懂
但是 cpu 需要的是能執(zhí)行的一條條字節(jié)碼指令
翻譯成字節(jié)碼
要把源程序翻譯成字節(jié)碼才能執(zhí)行
字節(jié)碼對(duì)應(yīng)著 cpu 的指令
怎么把 ast 轉(zhuǎn)化為字節(jié)碼 (指令) 呢?
需要編譯 (compile)
從一種語言到另一種語言
從 py 文件
到字節(jié)碼 (指令)
就是編譯

我可以看看這個(gè)編譯過程么?
compile

編譯結(jié)果
編譯 (compile) 之后得到是字節(jié)碼指令文件
所以擴(kuò)展名是 pyc
其中 c 代表 compiled
pyc 是字節(jié)碼 (bytecode) 文件
python 虛擬機(jī)的虛擬 cpu 就可以直接執(zhí)行了
先看看這個(gè) pyc 文件
注意他在
__pycache__
文件夾下cache 的意思是緩存
pycache 兩端各有 2 條下劃線 (_)
進(jìn)這個(gè)文件夾看看
進(jìn)入__pycache__
文件夾
打開 pyc 文件
得到的字節(jié)碼看起來完全是亂碼
可以想辦法看懂這些字節(jié)碼么?
vi 打開這個(gè)這個(gè) pyc 文件
二進(jìn)制形態(tài)
:set wrap 設(shè)置換行
這樣看到了他的字符串形態(tài)
可以看到他的二進(jìn)制字節(jié)形態(tài)么?
機(jī)器語言
:%!xxd
把文件轉(zhuǎn)化為字節(jié)形態(tài)
這純純的機(jī)器語言字節(jié)形態(tài)
實(shí)在是看不懂啊??
這真的是指令么?
究竟什么是指令呢?
指令
instruction
最早指的是教的行為或者過程
計(jì)算機(jī)領(lǐng)域里面特指指令
比如加法指令
減法指令
可以讓 cpu 做特定運(yùn)算的指令
由于計(jì)算機(jī)只認(rèn)識(shí) 0 和 1
所以要把這些加加減減的指令
對(duì)應(yīng)到 0 和 1 的二進(jìn)制形態(tài)上去
0 和 1 的二進(jìn)制形態(tài)我們記不住
于是有了匯編助記符
助記符告訴我們這條 0 和 1 的二進(jìn)制形態(tài)
到底對(duì)應(yīng)什么指令
助記符的語言就是匯編語言
匯編 assemble
assemble 指的是收集、集結(jié)
assembler 指的是裝卸工
在計(jì)算機(jī)中特指匯編語言
可以讓我們把 0 和 1 的機(jī)器指令
收集起來形成的助記符集合
就是匯編語言指令集
這就是匯編語言和 0101 的對(duì)應(yīng)關(guān)系
反編譯
disassemble
這個(gè)詞由兩部分組成
dis (反著來的)
?
dislike
disgrace
disagree
assembler (匯編語言)
disassemble 反編譯
把 py 源文件編譯成的字節(jié)碼 (指令) 我們?nèi)祟惪床幻靼?/p>
把這些字節(jié)碼 (指令) 反編譯 (disassemble) 成匯編語言助記符
有了助記符我們就知道指令的含義了
這可以用么?
去試試!
反編譯 (dis)
python3 -m disguido.py
-m 代表使用模塊
dis 代表反編譯 (disassemble)
我們可以看見
前面是行號(hào)
每行對(duì)應(yīng) 4 條指令
LOAD_NAME 裝載 (函數(shù)) 名字
LOAD_CONST 裝載常量
CALL_FUNCTION 調(diào)用函數(shù)
POP_TOP 彈棧
總共 6 句
對(duì)應(yīng) 6 組字節(jié)碼
每組兩個(gè)字節(jié)
那具體這個(gè) LOAD_NAME 是要做些什么呢?
指令
LOAD_NAME
把一個(gè)值壓入堆棧 co_names
把 print 這個(gè)函數(shù)名壓入了堆棧
一會(huì)兒就要調(diào)用這個(gè)被壓入堆棧的 print 函數(shù)
但是 LOAD_NAME 這條指令
具體對(duì)應(yīng)什么二進(jìn)制字節(jié)狀態(tài)呢?
這個(gè)去哪里找呢?
python 源頭
python 是從哪里來的呢?
python 是開源編程語言
整個(gè)的源代碼都是開放的
我們可以去 github 找到他的源代碼
https://github.com/python/cpython
二進(jìn)制狀態(tài)
搜索 LOAD_NAME 并且排查
找到字節(jié)碼狀態(tài)位置
指令對(duì)應(yīng)著一個(gè)字節(jié)碼狀態(tài)值
https://github.com/python/cpython/blob/main/Lib/opcode.py
這樣我們能否找到
4 條指令分別對(duì)應(yīng)的字節(jié)狀態(tài)值
4 條指令
指令助記符
指令含義
十進(jìn)制狀態(tài)
十六進(jìn)制狀態(tài)
LOAD_NAME
裝載函數(shù)名稱
101
0x65
LOAD_CONST
裝載參數(shù)
100
0x64
CALL_FUNCTION
調(diào)用函數(shù)
142
0x8e
POP_TOP
彈棧返回
1
0x01
可以找到源代碼的對(duì)應(yīng)關(guān)系么?

好像找到了
64XX
64 00 是從表中的 00 號(hào)位置取得字符串 "Guido in cwi"
64 01 是從表中的 01 號(hào)位置取字符串 "Guido in cnri"
...
以此類推,直到 05 83 取出字符串 "Guido in microsoft"
0x83 對(duì)應(yīng)的是 GET_AWAITABLE
可等待地調(diào)用
那這些二進(jìn)制代碼究竟是什么指令集的呢?
首先我們得弄懂什么是指令集呢?
指令集
指令集 就是
指令的集合

上圖是 arm 的指令集
也常被稱作 arm 架構(gòu)
那什么又是架構(gòu)呢?
architect
architect 原本的英文含義是
建筑

architecture
造房子的人
就是建筑師

在 cpu 領(lǐng)域
architect
architecture
指的是什么呢?
架構(gòu)師

架構(gòu)師
軟件開發(fā)行業(yè)從業(yè)者的終極形態(tài)
非常硬核的存在

那 python 的字節(jié)碼用的是什么架構(gòu)呢?
arm
還是 x86 呢?
虛擬機(jī)的虛擬 cpu
pyc 的這些字節(jié)碼 (bytecode)
對(duì)應(yīng)的是 python 虛擬機(jī)上面虛擬 cpu 的指令集

cpu 也能虛擬嗎?
我們先把這節(jié)課總結(jié)一下
總結(jié)
我們把 python 源文件
詞法分析 得到 詞流 (token stream)
語法分析 得到 抽象語法樹 (Abstract Syntax Tree)
編譯 得到 字節(jié)碼 (bytecode)
字節(jié)碼我們看不懂
所以反編譯 得到 指令文件 (opcode)

指令文件是基于 python 虛擬機(jī)的虛擬 cpu 的指令集
什么是 python 虛擬機(jī)呢???
我們下次再說??
藍(lán)橋->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