深入理解Python虛擬機中的Code?obejct
在本篇文章當中主要給大家深入介紹在 cpython 當中非常重要的一個數(shù)據(jù)結(jié)構(gòu) code object! 在上一篇文章 深入理解 python 虛擬機:pyc 文件結(jié)構(gòu) ,我們簡單介紹了一下在 code object 當中有哪些字段以及這些字段的簡單含義,在本篇文章當中將會舉一些例子以便更加深入理解這些字段。
Code Object 數(shù)據(jù)結(jié)構(gòu)
typedef struct { PyObject_HEAD int co_argcount; /* #arguments, except *args */ int co_kwonlyargcount; /* #keyword only arguments */ int co_nlocals; /* #local variables */ int co_stacksize; /* #entries needed for evaluation stack */ int co_flags; /* CO_..., see below */ PyObject *co_code; /* instruction opcodes */ PyObject *co_consts; /* list (constants used) */ PyObject *co_names; /* list of strings (names used) */ PyObject *co_varnames; /* tuple of strings (local variable names) */ PyObject *co_freevars; /* tuple of strings (free variable names) */ PyObject *co_cellvars; /* tuple of strings (cell variable names) */ /* The rest aren't used in either hash or comparisons, except for co_name (used in both) and co_firstlineno (used only in comparisons). This is done to preserve the name and line number for tracebacks and debuggers; otherwise, constant de-duplication would collapse identical functions/lambdas defined on different lines. */ unsigned char *co_cell2arg; /* Maps cell vars which are arguments. */ PyObject *co_filename; /* unicode (where it was loaded from) */ PyObject *co_name; /* unicode (name, for reference) */ int co_firstlineno; /* first source line number */ PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) See Objects/lnotab_notes.txt for details. */ void *co_zombieframe; /* for optimization only (see frameobject.c) */ PyObject *co_weakreflist; /* to support weakrefs to code objects */ } PyCodeObject;
下面是 code object 當中各個字段的作用:
- 首先需要了解一下代碼塊這個概念,所謂代碼塊就是一個小的 python 代碼,被當做一個小的單元整體執(zhí)行。在 python 當中常見的代碼塊塊有:函數(shù)體、類的定義、一個模塊。
- argcount,這個表示一個代碼塊的參數(shù)個數(shù),這個參數(shù)只對函數(shù)體代碼塊有用,因為函數(shù)可能會有參數(shù),比如上面的 pycdemo.py 是一個模塊而不是一個函數(shù),因此這個參數(shù)對應(yīng)的值為 0 。
- co_code,這個對象的具體內(nèi)容就是一個字節(jié)序列,存儲真實的 python 字節(jié)碼,主要是用于 python 虛擬機執(zhí)行的,在本篇文章當中暫時不詳細分析。
- co_consts,這個字段是一個列表類型的字段,主要是包含一些字符串常量和數(shù)值常量,比如上面的 "__main__" 和 100 。
- co_filename,這個字段的含義就是對應(yīng)的源文件的文件名。
- co_firstlineno,這個字段的含義為在 python 源文件當中第一行代碼出現(xiàn)的行數(shù),這個字段在進行調(diào)試的時候非常重要。
- co_flags,這個字段的主要含義就是標識這個 code object 的類型。0x0080 表示這個 block 是一個協(xié)程,0x0010 表示這個 code object 是嵌套的等等。
- co_lnotab,這個字段的含義主要是用于計算每個字節(jié)碼指令對應(yīng)的源代碼行數(shù)。
- co_varnames,這個字段的主要含義是表示在一個 code object 本地定義的一個名字。
- co_names,和 co_varnames 相反,表示非本地定義但是在 code object 當中使用的名字。
- co_nlocals,這個字段表示在一個 code object 當中本地使用的變量個數(shù)。
- co_stackszie,因為 python 虛擬機是一個棧式計算機,這個參數(shù)的值表示這個棧需要的最大的值。
- co_cellvars,co_freevars,這兩個字段主要和嵌套函數(shù)和函數(shù)閉包有關(guān),我們在后續(xù)的文章當中將詳細解釋這個字段。
CodeObject 詳細分析
現(xiàn)在我們使用一些實際的例子來分析具體的 code object 。
import dis import binascii import types d = 10 def test_co01(c): a = 1 b = 2 return a + b + c + d
在前面的文章當中我們提到過一個函數(shù)是包括一個 code object 對象,test_co01 的 code object 對象的輸出結(jié)果(完整代碼見co01)如下所示:
code argcount 1 nlocals 3 stacksize 2 flags 0043 0x43 code b'6401007d01006402007d02007c01007c0200177c0000177400001753' 9 0 LOAD_CONST 1 (1) 3 STORE_FAST 1 (a) 10 6 LOAD_CONST 2 (2) 9 STORE_FAST 2 (b) 11 12 LOAD_FAST 1 (a) 15 LOAD_FAST 2 (b) 18 BINARY_ADD 19 LOAD_FAST 0 (c) 22 BINARY_ADD 23 LOAD_GLOBAL 0 (d) 26 BINARY_ADD 27 RETURN_VALUE consts None 1 2 names ('d',) varnames ('c', 'a', 'b') freevars () cellvars () filename '/tmp/pycharm_project_396/co01.py' name 'test_co01' firstlineno 8 lnotab b'000106010601'
- 字段 argcount 的值等于 1,說明函數(shù)有一個參數(shù),這個函數(shù) test_co01 有一個參數(shù) c 是相互對應(yīng)的。
- 字段 nlocals 的值等于 3,說明在函數(shù) test_co01 當中一個一共實現(xiàn)了三個函數(shù)本地變量 a, b, c 。
- 字段 names,對應(yīng)代碼代碼當中的 co_names,根據(jù)前面的定義就是 d 這個全局變量在函數(shù) test_co01 當中使用,但是卻沒有在函數(shù)當中定義了。
- 字段 varnames,這個就表示在本地定義使用的變量了,在函數(shù) test_co01 當中主要有三個變量 a, b, c 。
- 字段 filename,就是 python 文件的地址了。
- 字段 firstlineno 說明函數(shù)的第一行出現(xiàn)在對應(yīng) python 代碼的 第 8 行。
Flags 字段詳細分析
我們具體使用 python3.5 的源代碼進行分析,在 cpython 虛擬機的具體實現(xiàn)如下所示(Include/code.h):
/* Masks for co_flags above */ #define CO_OPTIMIZED 0x0001 #define CO_NEWLOCALS 0x0002 #define CO_VARARGS 0x0004 #define CO_VARKEYWORDS 0x0008 #define CO_NESTED 0x0010 #define CO_GENERATOR 0x0020 /* The CO_NOFREE flag is set if there are no free or cell variables. This information is redundant, but it allows a single flag test to determine whether there is any extra work to be done when the call frame it setup. */ #define CO_NOFREE 0x0040 /* The CO_COROUTINE flag is set for coroutine functions (defined with ``async def`` keywords) */ #define CO_COROUTINE 0x0080 #define CO_ITERABLE_COROUTINE 0x0100
如果 flags 字段和上面的各個宏定義進行 & 運算,如果得到的結(jié)果大于 0,則說明符合對應(yīng)的條件。
上面的宏定義的含義如下所示:
- CO_OPTIMIZED,這個字段表示 code object 是被優(yōu)化過的,使用函數(shù)本地定義的變量。
- CO_NEWLOCALS,這個字段的含義為當這個 code object 的代碼被執(zhí)行的時候會給棧幀當中的 f_locals 對象創(chuàng)建一個 dict 對象。
- CO_VARARGS,表示這個 code object 對象是否含有位置參數(shù)。
- CO_VARKEYWORDS,表示這個 code object 是否含有關(guān)鍵字參數(shù)。
- CO_NESTED,表示這個 code object 是一個嵌套函數(shù)。
- CO_GENERATOR,表示這個 code object 是一個生成器。
- CO_COROUTINE,表示這個 code object 是一個協(xié)程函數(shù)。
- CO_ITERABLE_COROUTINE,表示 code object 是一個可迭代的協(xié)程函數(shù)。
- CO_NOFREE,這個表示沒有 freevars 和 cellvars,即沒有函數(shù)閉包。
現(xiàn)在再分析一下前面的函數(shù) test_co01 的 flags,他對應(yīng)的值等于 0x43,則說明這個函數(shù)滿足三個特性分別是 CO_NEWLOCALS,CO_OPTIMIZED 和 CO_NOFREE。
freevars & cellvars
我們使用下面的函數(shù)來對這兩個字段進行分析:
def test_co02(): a = 1 b = 2 def g(): return a + b return a + b + g()
上面的函數(shù)的信息如下所示(完整代碼見co02):
code argcount 0 nlocals 1 stacksize 3 flags 0003 0x3 code b'640100890000640200890100870000870100660200640300640400860000' b'7d0000880000880100177c00008300001753' 15 0 LOAD_CONST 1 (1) 3 STORE_DEREF 0 (a) 16 6 LOAD_CONST 2 (2) 9 STORE_DEREF 1 (b) 18 12 LOAD_CLOSURE 0 (a) 15 LOAD_CLOSURE 1 (b) 18 BUILD_TUPLE 2 21 LOAD_CONST 3 (<code object g at 0x7f133ff496f0, file "/tmp/pycharm_project_396/co01.py", line 18>) 24 LOAD_CONST 4 ('test_co02.<locals>.g') 27 MAKE_CLOSURE 0 30 STORE_FAST 0 (g) 20 33 LOAD_DEREF 0 (a) 36 LOAD_DEREF 1 (b) 39 BINARY_ADD 40 LOAD_FAST 0 (g) 43 CALL_FUNCTION 0 (0 positional, 0 keyword pair) 46 BINARY_ADD 47 RETURN_VALUE consts None 1 2 code argcount 0 nlocals 0 stacksize 2 flags 0013 0x13 code b'8800008801001753' 19 0 LOAD_DEREF 0 (a) 3 LOAD_DEREF 1 (b) 6 BINARY_ADD 7 RETURN_VALUE consts None names () varnames () freevars ('a', 'b') cellvars () filename '/tmp/pycharm_project_396/co01.py' name 'g' firstlineno 18 lnotab b'0001' 'test_co02.<locals>.g' names () varnames ('g',) freevars () cellvars ('a', 'b') filename '/tmp/pycharm_project_396/co01.py' name 'test_co02' firstlineno 14 lnotab b'0001060106021502'
從上面的輸出我們可以看到的是,函數(shù) test_co02 的 cellvars 為 ('a', 'b'),函數(shù) g 的 freevars 為 ('a', 'b'),cellvars 表示在其他函數(shù)當中會使用本地定義的變量,freevars 表示本地會使用其他函數(shù)定義的變量。
再來分析一下函數(shù) test_co02 的 flags,他的 flags 等于 0x3 因為有閉包的存在因此 flags 不會存在 CO_NOFREE,也就是少了值 0x0040 。
stacksize
這個字段存儲的是在函數(shù)在被虛擬機執(zhí)行的時候所需要的最大的??臻g的大小,這也是一種優(yōu)化手段,因為在知道所需要的最大的??臻g,所以可以在函數(shù)執(zhí)行的時候直接分配指定大小的空間不需要在函數(shù)執(zhí)行的時候再去重新擴容。
def test_stack(): a = 1 b = 2 return a + b
上面的代碼相關(guān)字節(jié)碼等信息如下所示:
code argcount 0 nlocals 2 stacksize 2 flags 0043 0x43 code b'6401007d00006402007d01007c00007c01001753' # 字節(jié)碼指令 # 字節(jié)碼指令參數(shù) # 參數(shù)對應(yīng)的值 24 0 LOAD_CONST 1 (1) 3 STORE_FAST 0 (a) 25 6 LOAD_CONST 2 (2) 9 STORE_FAST 1 (b) 26 12 LOAD_FAST 0 (a) 15 LOAD_FAST 1 (b) 18 BINARY_ADD 19 RETURN_VALUE consts None # 下標等于 0 的常量 1 # 下標等于 1 的常量 2 # 下標等于 2 的常量 names () varnames ('a', 'b') freevars () cellvars ()
我們現(xiàn)在來模擬一下執(zhí)行過程,在模擬之前我們首先來了解一下上面幾條字節(jié)碼的作用:
LOAD_CONST,將常量表當中的下標等于 i 個對象加載到棧當中,對應(yīng)上面的代碼 LOAD_CONST 的參數(shù) i = 1。因此加載測常量等于 1 。因此現(xiàn)在??臻g如下所示:
STORE_FAST,將棧頂元素彈出并且保存到 co_varnames 對應(yīng)的下標當中,根據(jù)上面的字節(jié)碼參數(shù)等于 0 ,因此將 1 保存到 co_varnames[0] 對應(yīng)的對象當中。
LOAD_CONST,將下標等于 2 的常量加載進入棧中。
STORE_FAST,將棧頂元素彈出,并且保存到 varnames 下標為 1 的對象。
LOAD_FAST,是取出 co_varnames 對應(yīng)下標的數(shù)據(jù),并且將其壓入棧中。我們直接連續(xù)執(zhí)行兩個 LOAD_FAST 之后??臻g的布局如下:
BINARY_ADD,這個字節(jié)碼指令是將??臻g的兩個棧頂元素彈出,然后將兩個數(shù)據(jù)進行相加操作,然后將相加得到的結(jié)果重新壓入棧中。
RETURN_VALUE,將棧頂元素彈出并且作為返回值返回。
從上面的整個執(zhí)行過程來看整個??臻g使用的最大的空間長度為 2 ,因此 stacksize = 2 。
總結(jié)
在本篇文章當中主要分析了一些 code obejct 當中比較重要的字段,code object 是 cpython 虛擬機當中一個比較重要的數(shù)據(jù)結(jié)構(gòu),深入的去理解這里面的字段對于我們理解 python 虛擬機非常有幫助。
以上就是深入理解Python虛擬機中的Code obejct的詳細內(nèi)容,更多關(guān)于Python虛擬機Code obejct的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Django 創(chuàng)建后臺,配置sqlite3教程
今天小編就為大家分享一篇Django 創(chuàng)建后臺,配置sqlite3教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11使用Python腳本對GiteePages進行一鍵部署的使用說明
剛好之前有了解過python的自動化,就想著自動化腳本,百度一搜還真有類似的文章。今天就給大家分享下使用Python腳本對GiteePages進行一鍵部署的使用說明,感興趣的朋友一起看看吧2021-05-05Python數(shù)據(jù)分析?Pandas?Series對象操作
這篇文章主要介紹了Python數(shù)據(jù)分析之Pandas?Series對象,文章基于python的相關(guān)資料展開詳細內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-05-05python獲取指定日期范圍內(nèi)的每一天,每個月,每季度的方法
這篇文章主要介紹了python獲取指定日期范圍內(nèi)的每一天,每個月,每季度的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Python數(shù)據(jù)可視化Pyecharts庫的使用教程
pyecharts是一個用于生成echarts圖表的類庫。echarts是百度開源的一個數(shù)據(jù)可視化庫,用echarts生成的圖可視化效果非常棒。使用pyechart庫可以在python中生成echarts數(shù)據(jù)圖。本文將詳細介紹一下Pyecharts庫的使用,需要的可以參考一下2022-02-02Anaconda環(huán)境GDAL庫基于whl文件的配置方法
這篇文章主要介紹了Anaconda環(huán)境GDAL庫基于whl文件的配置方法,我們介紹了基于conda?install命令直接聯(lián)網(wǎng)安裝GDAL庫的方法,本文給大家介紹的非常詳細,需要的朋友可以參考下2023-04-04Python 實現(xiàn)Windows開機運行某軟件的方法
今天小編就為大家分享一篇Python 實現(xiàn)Windows開機運行某軟件的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-10-10