[oeasy]python0135_python_語(yǔ)義分析_ast_抽象語(yǔ)法樹(shù)_abstract_syntax_tree
語(yǔ)義分析_抽象語(yǔ)法樹(shù)_反匯編
回憶
上次回顧了一下歷史
python 是如何從無(wú)到有的
看到 Guido 長(zhǎng)期的堅(jiān)持和努力
python究竟是如何理解
print("hello")的?
這些ascii字母如何被組織起來(lái)執(zhí)行?
純文本
首先編寫(xiě)Guido的簡(jiǎn)歷
并保存為Guido.py
生成token流
回到shell之后
從字符流生成token流
這個(gè)過(guò)程叫做分詞
分詞
首先把一個(gè)個(gè)字符組成詞
分析一下哪些字可以組成詞
術(shù)語(yǔ)叫詞法分析(lexical analysis)
詞分析出來(lái)之后呢?
組詞
詞分析出來(lái)就是怎么組詞的問(wèn)題
哪些詞和哪些詞先組合
哪些詞和哪些詞后組合
生成一棵抽象語(yǔ)法樹(shù)
AST(Abstract Syntax Tree)
我能看看這棵ast樹(shù)么?
引入ast模塊
具體怎么做呢?
流程
先把這個(gè)ast模塊導(dǎo)入(import)進(jìn)來(lái)
第一句就是import ast
回車之后沒(méi)有任何報(bào)錯(cuò)
那就是執(zhí)行成功了
后面也一樣
沒(méi)有報(bào)錯(cuò)就是執(zhí)行成功了
然后讀取guido.py并送到s
然后對(duì)于s進(jìn)行語(yǔ)法分析(parse)
再把分析(parse)的結(jié)果進(jìn)行轉(zhuǎn)儲(chǔ)(dump)
看起來(lái)有點(diǎn)亂
可以清晰一些么?
升級(jí)Python
目前l(fā)anqiao.cn上面的python是3.8
這個(gè)清晰縮進(jìn)的格式需要在3.9以上完成
需要升級(jí)
升級(jí)之后就可以使用Python3.9了
縮進(jìn)換行
只能在本地演示一下
這個(gè)就是把詞組成語(yǔ)法樹(shù)的樣子
如何理解這棵樹(shù)呢?
我們看一個(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)
其他什么也沒(méi)加
表達(dá)式是1 + 2 * 3
后兩個(gè)會(huì)先結(jié)合
得到的結(jié)果 作為下一個(gè)運(yùn)算的 右操作數(shù)
然后再和1 進(jìn)行 加法運(yùn)算
有了 語(yǔ)法樹(shù)
下一步 要做什么呢?
這棵語(yǔ)法樹(shù) 我們能看懂
能執(zhí)行的 一條條字節(jié)碼指令
但是cpu 需要的是
翻譯成 字節(jié)碼
要把源程序 翻譯成字節(jié)碼 才能執(zhí)行
字節(jié)碼 對(duì)應(yīng)著cpu的指令
怎么把a(bǔ)st 轉(zhuǎn)化為字節(jié)碼(指令) 呢?
需要 編譯(compile)
從一種語(yǔ)言 到 另一種語(yǔ)言
compile
從py文件
到字節(jié)碼(指令)
就是編譯
compile
我可以看看這個(gè)編譯過(guò)程么?
指令
instruction
python3 -m dis Guido.py
-m 代表使用模塊
dis 代表反編譯(disassemble)
我們可以看見(jiàn)
LOAD_NAME 裝載(函數(shù))名字
LOAD_CONST 裝載常量
CALL_FUNCTION 調(diào)用函數(shù)
POP_TOP 彈棧
前面是行號(hào)
每行對(duì)應(yīng)4條指令
編譯結(jié)果
先看看這個(gè)pyc文件
注意他在
__pycache__
文件夾下
:%!xxd
把文件轉(zhuǎn)化為字節(jié)形態(tài)
這純純的機(jī)器語(yǔ)言字節(jié)形態(tài)
實(shí)在是看不懂啊??
這真的是指令么?
究竟什么是指令呢?
指令
py文件每行print 對(duì)應(yīng)4條指令
LOAD_NAME 裝載(函數(shù))名字
LOAD_CONST 裝載常量
CALL_FUNCTION 調(diào)用函數(shù)
POP_TOP 彈棧
https://github.com/python/cpython/blob/main/Lib/opcode.py
這樣 我們 能否找到
4條指令 分別對(duì)應(yīng)的 字節(jié)狀態(tài)值
找到對(duì)應(yīng)關(guān)系
指令助記符指令含義十進(jìn)制狀態(tài)十六進(jìn)制狀態(tài)LOAD_NAME裝載函數(shù)名稱1010x65LOAD_CONST裝載參數(shù)1000x64CALL_FUNCTION調(diào)用函數(shù)1420x8ePOP_TOP彈棧返回10x01
可以找對(duì)應(yīng)關(guān)系
我們從頭捋一下
python3 執(zhí)行過(guò)程
不管是python3這個(gè)游樂(lè)場(chǎng)
還是Guido.py這個(gè)python程序
都在我們的硬盤(pán)上
先得把文件從硬盤(pán)讀到內(nèi)存
python3 執(zhí)行的過(guò)程大致是這樣
先把python3.8這個(gè)主解釋器
加載到內(nèi)存中
然后 在x86-64的cpu上 執(zhí)行
模擬出 一臺(tái)python虛擬機(jī)
準(zhǔn)備開(kāi)始 對(duì)py文件 解釋執(zhí)行
先編譯
然后把參數(shù)
Guido.py
這個(gè)需要執(zhí)行的程序 加載到內(nèi)存詞法分析 得到 詞流(token stream)
語(yǔ)法分析 得到 抽象語(yǔ)法樹(shù)(Abstract Syntax Tree)
編譯 得到 字節(jié)碼 (byte_code)
也就是編譯后 的pyc文件
解釋執(zhí)行
不過(guò) 這個(gè)pyc指令文件
是基于python虛擬機(jī)的 虛擬cpu的 指令集的
需要放到 模擬好的 python虛擬機(jī)中
一條條指令 進(jìn)行執(zhí)行
換句話說(shuō)
簡(jiǎn)化版的 hello.py 的執(zhí)行過(guò)程是:
給了
python3
一個(gè)參數(shù)Guido.py
使用
python3
這個(gè)解釋器來(lái)解釋執(zhí)行Guido.py
Guido.py
中的語(yǔ)句一句句地依次解釋執(zhí)行全解釋完成 后
退出python這個(gè)程序
把控制權(quán)交回到shell
這些 都是基于 解釋器python3的
先編譯成 python虛擬機(jī)的 虛擬指令字節(jié)碼
然后用 python虛擬機(jī) 直接執(zhí)行虛擬指令
所謂的 解釋器python3
而解釋器(python3) 是
在不同系統(tǒng) 不同架構(gòu)的cpu語(yǔ)言上 運(yùn)行的
那不同的系統(tǒng)、cpu架構(gòu)
python3 為什么 都能正確地解釋?
總結(jié)
這次把py源文件
這里確立了優(yōu)先級(jí)
詞法分析 得到 詞流(token stream)
語(yǔ)法分析 得到 抽象語(yǔ)法樹(shù)(Abstract Syntax Tree)
編譯 得到 字節(jié)碼 (bytecode)
字節(jié)碼我們看不懂
指令文件是基于python虛擬機(jī)的虛擬cpu的指令集
所以反編譯 得到 指令文件(opcode)
先從 python3最基礎(chǔ)的
python虛擬機(jī)是如何做的???
變量聲明和賦值來(lái)看看
我們下次再說(shuō)??
藍(lán)橋->https://www.lanqiao.cn/courses/3584
github->https://github.com/overmind1980/oeasy-python-tutorial
gitee->https://gitee.com/overmind1980/oeasypython