python代碼是如何執(zhí)行的?
解釋運(yùn)行程序 ??
回憶上次內(nèi)容
py
文件的程序是按照順序一行行挨排解釋執(zhí)行的
我們可以
python3 -m pdb hello.py
來對(duì)程序調(diào)試調(diào)試的目的是去除
bug
別害怕
bug
bug
會(huì)有提示我們也就知道如何
debug
調(diào)試順序執(zhí)行
程序在文本中從上到下是一行行寫的
調(diào)試的時(shí)候也是從頭到尾一行行執(zhí)行的
但是執(zhí)行的時(shí)候是如何把代碼一行行解釋執(zhí)行的呢?
說到底
python3
到底是個(gè)啥呢???python3
又是怎么解釋hello.py
的?這兩這節(jié)課相當(dāng)復(fù)雜
如果感覺太過復(fù)雜
可以直接跳過
不影響后面的理解 ??
我們先要看看python3對(duì)Guido.py做了些什么???
tokenize
首先把字符分組成詞
詞法分析(lexical analysis)中

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

詞法分析之后輸出的是一個(gè)token流
什么是token流呢?
首先要知道什么是token
token
token
令牌

古人說聽我號(hào)令
急急如律令
令行禁止
號(hào)指的是號(hào)角
令指的是令牌
怎么把源文件變成一個(gè)token流呢?
python3模塊
這個(gè)東西是python3的一個(gè)模塊

具體怎么運(yùn)行呢?
token流
我們嘗試運(yùn)行
python3 -m tokenize guido.py
對(duì)guido.py進(jìn)行詞法分析
分析出來的詞(token)流長(zhǎng)什么樣子呢?

這個(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行...

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

具體怎么生成這棵ast樹呢?
引入ast模塊

具體怎么做呢?
流程
先把這個(gè)ast模塊導(dǎo)入(import)進(jìn)來
然后讀取guido.py并送到s
然后對(duì)于s進(jìn)行語(yǔ)法分析(parse)

不過這亂七八糟堆一起怎么理解呢?
縮進(jìn)換行
把分析的結(jié)果進(jìn)行dump(轉(zhuǎn)儲(chǔ))

目前l(fā)anqiao.cn上面的python是3.8
這個(gè)換行需要在3.9以上完成
只能在本地演示一下
縮進(jìn)演示

這個(gè)就是把詞組成語(yǔ)法樹的樣子
但是語(yǔ)法樹還不能直接執(zhí)行
什么才能直接執(zhí)行呢?
翻譯成字節(jié)碼
字節(jié)碼(指令)才能真正執(zhí)行
怎么把a(bǔ)st轉(zhuǎn)化為字節(jié)碼(指令)呢?
需要編譯
從一種語(yǔ)言到另一種語(yǔ)言
從py文件
到字節(jié)碼(指令)

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


這個(gè)東西完全是亂碼
我看不懂???
vi打開這個(gè)這個(gè)pyc文件
二進(jìn)制形態(tài)
:set wrap設(shè)置換行

可以看到他的二進(jìn)制形態(tài)么?
二進(jìn)制
:%!xxd
把文件轉(zhuǎn)化為二進(jìn)制

實(shí)在是看不懂啊
能把這個(gè)字節(jié)碼(指令)變成我們?nèi)四芸炊拿矗?/p>
反編譯
disassembler這個(gè)詞由兩部分組成
dis (反著來的)
assembler (匯編語(yǔ)言)
整體就是
把py源文件編譯成的字節(jié)碼(指令)
反編譯(disassembler)成這些字節(jié)碼對(duì)應(yīng)的助記符(指令的含義)

這可以用么?
去試試!
反編譯(dis)
python3 -m dis guido.py

我們可以看見
LOAD_NAME 裝載函數(shù)名
LOAD_CONST 裝載參數(shù)
CALL_FUNCTION 調(diào)用函數(shù)
POP_TOP 彈棧返回
前面是行號(hào)
每行對(duì)應(yīng)4條指令
每條指令對(duì)應(yīng)一個(gè)字節(jié)碼
那具體這個(gè)LOAD_NAME是什么意思呢?
指令
指令對(duì)應(yīng)著一個(gè)字節(jié)碼狀態(tài)

但是LOAD_NAME這條指令
具體對(duì)應(yīng)什么二進(jìn)制字節(jié)狀態(tài)呢?
二進(jìn)制狀態(tài)

我們找找程序中的4條指令對(duì)應(yīng)的字節(jié)狀態(tài)
4條指令
指令助記符指令含義十進(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)系么?
好像找到了
但是0x83 對(duì)應(yīng)的是 GET_AWAITABLE
顯然00 83是從表中的0號(hào)位置取得字符串變量
01 83是從表中的1號(hào)位置取字符串
以此類推,直到05 83
那這些代碼究竟是什么指令集的呢?
龍芯
intel
還是arm呢?
虛擬機(jī)的虛擬cpu
這些字節(jié)碼(bytecode)對(duì)應(yīng)的是python虛擬機(jī)上面虛擬cpu的指令集
怎么還有虛擬機(jī)
虛擬cpu呢?
我們先把這節(jié)課總結(jié)一下
總結(jié)
我們把python源文件
詞法分析 得到 詞流(token stream)
語(yǔ)法分析 得到 抽象語(yǔ)法樹(Abstract Syntax Tree)
編譯 得到 字節(jié)碼 (bytecode)
反編譯 得到 指令文件
不過這個(gè)指令文件是基于虛擬機(jī)的虛擬cpu的指令集
怎么這么虛呢???
我們下次再說*
?
本文章來自于《oeasy教您玩轉(zhuǎn)python》(https://www.lanqiao.cn/courses/3584)中第6個(gè)實(shí)驗(yàn)。
在 BV1bU4y1R7fp 中獲得了@小劉不是程序員 的啟發(fā),感謝!
希望有更多的這類深度編程愛好者的up主。