天天看點

「oeasy」python0010 - python虛拟機解釋執行py檔案的原理

解釋運作程式

回憶上次内容

  • 我們這次設定了斷點
    • 設定斷點的目的是更快地調試
    • 調試的目的是去除 bug
    • 别害怕 bug
    • 一步步地總能找到 bug
    • 這就是程式員基本功
      • 調試 debug
  • 我心中還是有疑問
    • python3 是怎麼解釋 hello.py 的?

純文字

  • 我們的py檔案是一個純文字檔案
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 打開我們的guido.py
    • 如果沒有就新做一個
  • 這裡面是一個個的字元
print("1982------Guido in cwi")
print("1995------Guido in cnri")
print("2000------Guido in beopen")
print("2005------Guido in google")
print("2012------Guido in dropbox")
print("2020------Guido in microsoft")           
  • python怎知道如何執行呢?

傳統文本

  • 傳統文本的基礎也是字元
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 在字元的基礎上組織起篇章結構
    • 字組成詞
    • 詞組成句
    • 句組成段
    • 段組成章節
    • 最後成書

tokenize

  • 首先把一個個字元組成詞
  • 分析一下哪些字可以組成詞
    • 術語叫詞法分析(lexical analysis)
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 把原來的字元流
    • 變成了詞的流
    • token(令牌)流
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 詞法分析之後輸出的是一個詞(token)的流
    • 啥是token呢?

token

  • token
    • 令牌
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 古人說聽我号令
    • 号指的是
      • 号角
      • 摔杯為号
      • 是一個信号
    • 令指的是令牌
      • 急急如律令
      • 打五十大闆
      • 令行禁止
  • 怎麼把源檔案變成一個詞(token)流呢?

python3子產品

  • 幫助手冊裡面有這個内容
  • 這個tokenize是python3的一個子產品(module)
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 具體怎麼運作呢?

token流

「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 我們嘗試運作
    • python3 -m tokenize guido.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(換行符)
    • 換行符意味着第一行結束
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 第2行...
  • 詞分析出來之後呢?

組詞

  • 詞分析出來就是怎麼組詞的問題
    • 哪些詞和哪些詞先組合
    • 哪些詞和哪些詞後組合
  • 生成一棵抽象文法樹
    • AST(Abstract Syntax Tree)
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 我能看看這棵ast樹麼?

引入ast子產品

「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 具體怎麼做呢?

流程

  • 先把這個ast子產品導入(import)進來
    • 第一句就是import ast
    • 回車之後沒有任何報錯
    • 那就是執行成功了
    • 後面也一樣
    • 沒有報錯就是執行成功了
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 然後讀取guido.py并送到s
  • 然後對于s進行文法分析(parse)
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 再把分析(parse)的結果進行轉儲(dump)
  • 看起來有點亂
    • 可以清晰一些麼?

更新Python

  • 目前lanqiao.cn上面的python是3.8
  • 這個清晰縮進的格式需要在3.9以上完成
  • 需要更新
sudo apt update
sudo apt install python3.9           
  • 更新之後就可以使用Python3.9了
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理

縮進換行

  • 隻能在本地示範一下
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 這個就是把詞組成文法樹的樣子
  • 如何了解這棵樹呢?
  • 我們看一個例子

表達式運算

  • 如果給的表達式為 1 2 3
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 結合序為下圖
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 前兩個先結合
  • 得到的結果作為下一個運算的左操作數
  • 然後和第3個結合

結合序

  • 如果把 第一個* 改成 + 号
  • 其他什麼也沒加
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 表達式是1 + 2 * 3
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 後兩個會先結合
  • 得到的結果作為下一個運算的右操作數
  • 然後再和1進行加法運算
  • 有了文法樹
    • 下一步要做什麼呢?
  • 這棵文法樹我們能看懂
  • 但是cpu需要的是能執行的一條條位元組碼指令

翻譯成位元組碼

  • 要把源程式翻譯成位元組碼才能執行
    • 位元組碼對應着cpu的指令
  • 怎麼把ast轉化為位元組碼(指令)呢?
    • 需要編譯(compile)
  • 從一種語言到另一種語言
    • 從py檔案
    • 到位元組碼(指令)
    • 就是編譯
  • 我可以看看這個編譯過程麼?

compile

「oeasy」python0010 - python虛拟機解釋執行py檔案的原理

編譯結果

  • 編譯(compile)之後得到是位元組碼指令檔案
    • 是以擴充名是pyc
    • 其中c代表compiled
    • pyc是位元組碼(bytecode)檔案
    • python虛拟機的虛拟cpu就可以直接執行了
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 先看看這個pyc檔案
    • 注意他在__pycache__檔案夾下
    • cache的意思是緩存
    • pycache兩端各有2條下劃線(_)
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 進這個檔案夾看看

進入__pycache__檔案夾

  • 打開pyc檔案
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 得到的位元組碼看起來完全是亂碼
    • 可以想辦法看懂這些位元組碼麼?
  • vi打開這個這個pyc檔案

二進制形态

  • :set wrap設定換行
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 這樣看到了他的字元串形态
  • 可以看到他的二進制位元組形态麼?

機器語言

  • :%!xxd
    • 把檔案轉化為位元組形态
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 這純純的機器語言位元組形态
    • 實在是看不懂啊
    • 這真的是指令麼?
  • 究竟什麼是指令呢?

指令

  • instruction
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 最早指的是教的行為或者過程
  • 計算機領域裡面特指指令
    • 比如加法指令
    • 減法指令
    • 可以讓cpu做特定運算的指令
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 由于計算機隻認識0和1
  • 是以要把這些加加減減的指令
  • 對應到0和1的二進制形态上去
  • 0和1的二進制形态我們記不住
  • 于是有了彙編助記符
  • 助記符告訴我們這條0和1的二進制形态
  • 到底對應什麼指令
  • 助記符的語言就是彙編語言

彙編assemble

「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • assemble指的是收集、集結
    • assembler指的是裝卸工
  • 在計算機中特指彙編語言
    • 可以讓我們把0和1的機器指令
    • 收集起來形成的助記符集合
    • 就是彙編語言指令集
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 這就是彙編語言和0101的對應關系

反編譯

  • disassemble
  • 這個詞由兩部分組成
    • dis (反着來的)
      • dislike
      • disgrace
      • disagree
    • assembler (彙編語言)
      • disassemble 反編譯
  • 把py源檔案編譯成的位元組碼(指令)我們人類看不明白
    • 把這些位元組碼(指令)反編譯(disassemble)成彙編語言助記符
    • 有了助記符我們就知道指令的含義了
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 這可以用麼?
    • 去試試!

反編譯(dis)

  • python3 -m dis guido.py
    • -m 代表使用子產品
    • dis 代表反編譯(disassemble)
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 我們可以看見
    • 前面是行号
    • 每行對應4條指令
      • LOAD_NAME 裝載(函數)名字
      • LOAD_CONST 裝載常量
      • CALL_FUNCTION 調用函數
      • POP_TOP 彈棧
  • 總共6句
    • 對應6組位元組碼
    • 每組兩個位元組
  • 那具體這個 LOAD_NAME 是要做些什麼呢?

指令

  • LOAD_NAME
    • 把一個值壓入堆棧co_names
    • 把print這個函數名壓入了堆棧
    • 一會兒就要調用這個被壓入堆棧的print函數
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 但是LOAD_NAME這條指令
    • 具體對應什麼二進制位元組狀态呢?
  • 這個去哪裡找呢?

python源頭

  • python是從哪裡來的呢?
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • python 是開源程式設計語言
  • 整個的源代碼都是開放的
  • 我們可以去github找到他的源代碼
    • https://github.com/python/cpython

二進制狀态

  • 搜尋LOAD_NAME并且排查
    • 找到位元組碼狀态位置
  • 指令對應着一個位元組碼狀态值
    • https://github.com/python/cpython/blob/main/Lib/opcode.py
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 這樣我們能否找到
  • 4條指令分别對應的位元組狀态值

4條指令

指令助記符 指令含義 十進制狀态 十六進制狀态
LOAD_NAME 裝載函數名稱 101 0x65
LOAD_CONST 裝載參數 100 0x64
CALL_FUNCTION 調用函數 142 0x8e
POP_TOP 彈棧傳回 1 0x01
  • 可以找到源代碼的對應關系麼?
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 好像找到了
  • 64XX
    • 64 00是從表中的00号位置取得字元串"Guido in cwi"
    • 64 01是從表中的01号位置取字元串"Guido in cnri"
    • ...
    • 以此類推,直到05 83取出字元串"Guido in microsoft"
  • 0x83 對應的是 GET_AWAITABLE
    • 可等待地調用
  • 那這些二進制代碼究竟是什麼指令集的呢?
  • 首先我們得弄懂什麼是指令集呢?

指令集

  • 指令集 就是
    • 指令的集合
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 上圖是arm的指令集
  • 也常被稱作arm架構
  • 那什麼又是架構呢?

architect

  • architect原本的英文含義是
    • 建築
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • architecture
    • 造房子的人
    • 就是建築師
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 在cpu領域
    • architect
    • architecture
  • 指的是什麼呢?

架構師

「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 架構師
    • 軟體開發行業從業者的終極形态
    • 非常硬核的存在
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 那python的位元組碼用的是什麼架構呢?
    • arm
    • 還是x86呢?

虛拟機的虛拟cpu

  • pyc的這些位元組碼(bytecode)
    • 對應的是python虛拟機上面虛拟cpu的指令集
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • cpu也能虛拟嗎?
    • 我們先把這節課總結一下

總結

  • 我們把python源檔案
    • 詞法分析 得到 詞流(token stream)
    • 文法分析 得到 抽象文法樹(Abstract Syntax Tree)
    • 編譯 得到 位元組碼 (bytecode)
    • 位元組碼我們看不懂
    • 是以反編譯 得到 指令檔案(opcode)
「oeasy」python0010 - python虛拟機解釋執行py檔案的原理
  • 指令檔案是基于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