深入理解Python虛擬機(jī)中的Code?obejct
在本篇文章當(dāng)中主要給大家深入介紹在 cpython 當(dāng)中非常重要的一個(gè)數(shù)據(jù)結(jié)構(gòu) code object! 在上一篇文章 深入理解 python 虛擬機(jī):pyc 文件結(jié)構(gòu) ,我們簡(jiǎn)單介紹了一下在 code object 當(dāng)中有哪些字段以及這些字段的簡(jiǎn)單含義,在本篇文章當(dāng)中將會(huì)舉一些例子以便更加深入理解這些字段。
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 當(dāng)中各個(gè)字段的作用:
- 首先需要了解一下代碼塊這個(gè)概念,所謂代碼塊就是一個(gè)小的 python 代碼,被當(dāng)做一個(gè)小的單元整體執(zhí)行。在 python 當(dāng)中常見(jiàn)的代碼塊塊有:函數(shù)體、類(lèi)的定義、一個(gè)模塊。
- argcount,這個(gè)表示一個(gè)代碼塊的參數(shù)個(gè)數(shù),這個(gè)參數(shù)只對(duì)函數(shù)體代碼塊有用,因?yàn)楹瘮?shù)可能會(huì)有參數(shù),比如上面的 pycdemo.py 是一個(gè)模塊而不是一個(gè)函數(shù),因此這個(gè)參數(shù)對(duì)應(yīng)的值為 0 。
- co_code,這個(gè)對(duì)象的具體內(nèi)容就是一個(gè)字節(jié)序列,存儲(chǔ)真實(shí)的 python 字節(jié)碼,主要是用于 python 虛擬機(jī)執(zhí)行的,在本篇文章當(dāng)中暫時(shí)不詳細(xì)分析。
- co_consts,這個(gè)字段是一個(gè)列表類(lèi)型的字段,主要是包含一些字符串常量和數(shù)值常量,比如上面的 "__main__" 和 100 。
- co_filename,這個(gè)字段的含義就是對(duì)應(yīng)的源文件的文件名。
- co_firstlineno,這個(gè)字段的含義為在 python 源文件當(dāng)中第一行代碼出現(xiàn)的行數(shù),這個(gè)字段在進(jìn)行調(diào)試的時(shí)候非常重要。
- co_flags,這個(gè)字段的主要含義就是標(biāo)識(shí)這個(gè) code object 的類(lèi)型。0x0080 表示這個(gè) block 是一個(gè)協(xié)程,0x0010 表示這個(gè) code object 是嵌套的等等。
- co_lnotab,這個(gè)字段的含義主要是用于計(jì)算每個(gè)字節(jié)碼指令對(duì)應(yīng)的源代碼行數(shù)。
- co_varnames,這個(gè)字段的主要含義是表示在一個(gè) code object 本地定義的一個(gè)名字。
- co_names,和 co_varnames 相反,表示非本地定義但是在 code object 當(dāng)中使用的名字。
- co_nlocals,這個(gè)字段表示在一個(gè) code object 當(dāng)中本地使用的變量個(gè)數(shù)。
- co_stackszie,因?yàn)?python 虛擬機(jī)是一個(gè)棧式計(jì)算機(jī),這個(gè)參數(shù)的值表示這個(gè)棧需要的最大的值。
- co_cellvars,co_freevars,這兩個(gè)字段主要和嵌套函數(shù)和函數(shù)閉包有關(guān),我們?cè)诤罄m(xù)的文章當(dāng)中將詳細(xì)解釋這個(gè)字段。
CodeObject 詳細(xì)分析
現(xiàn)在我們使用一些實(shí)際的例子來(lái)分析具體的 code object 。
import dis
import binascii
import types
d = 10
def test_co01(c):
a = 1
b = 2
return a + b + c + d
在前面的文章當(dāng)中我們提到過(guò)一個(gè)函數(shù)是包括一個(gè) code object 對(duì)象,test_co01 的 code object 對(duì)象的輸出結(jié)果(完整代碼見(jiàn)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,說(shuō)明函數(shù)有一個(gè)參數(shù),這個(gè)函數(shù) test_co01 有一個(gè)參數(shù) c 是相互對(duì)應(yīng)的。
- 字段 nlocals 的值等于 3,說(shuō)明在函數(shù) test_co01 當(dāng)中一個(gè)一共實(shí)現(xiàn)了三個(gè)函數(shù)本地變量 a, b, c 。
- 字段 names,對(duì)應(yīng)代碼代碼當(dāng)中的 co_names,根據(jù)前面的定義就是 d 這個(gè)全局變量在函數(shù) test_co01 當(dāng)中使用,但是卻沒(méi)有在函數(shù)當(dāng)中定義了。
- 字段 varnames,這個(gè)就表示在本地定義使用的變量了,在函數(shù) test_co01 當(dāng)中主要有三個(gè)變量 a, b, c 。
- 字段 filename,就是 python 文件的地址了。
- 字段 firstlineno 說(shuō)明函數(shù)的第一行出現(xiàn)在對(duì)應(yīng) python 代碼的 第 8 行。
Flags 字段詳細(xì)分析
我們具體使用 python3.5 的源代碼進(jìn)行分析,在 cpython 虛擬機(jī)的具體實(shí)現(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 字段和上面的各個(gè)宏定義進(jìn)行 & 運(yùn)算,如果得到的結(jié)果大于 0,則說(shuō)明符合對(duì)應(yīng)的條件。
上面的宏定義的含義如下所示:
- CO_OPTIMIZED,這個(gè)字段表示 code object 是被優(yōu)化過(guò)的,使用函數(shù)本地定義的變量。
- CO_NEWLOCALS,這個(gè)字段的含義為當(dāng)這個(gè) code object 的代碼被執(zhí)行的時(shí)候會(huì)給棧幀當(dāng)中的 f_locals 對(duì)象創(chuàng)建一個(gè) dict 對(duì)象。
- CO_VARARGS,表示這個(gè) code object 對(duì)象是否含有位置參數(shù)。
- CO_VARKEYWORDS,表示這個(gè) code object 是否含有關(guān)鍵字參數(shù)。
- CO_NESTED,表示這個(gè) code object 是一個(gè)嵌套函數(shù)。
- CO_GENERATOR,表示這個(gè) code object 是一個(gè)生成器。
- CO_COROUTINE,表示這個(gè) code object 是一個(gè)協(xié)程函數(shù)。
- CO_ITERABLE_COROUTINE,表示 code object 是一個(gè)可迭代的協(xié)程函數(shù)。
- CO_NOFREE,這個(gè)表示沒(méi)有 freevars 和 cellvars,即沒(méi)有函數(shù)閉包。
現(xiàn)在再分析一下前面的函數(shù) test_co01 的 flags,他對(duì)應(yīng)的值等于 0x43,則說(shuō)明這個(gè)函數(shù)滿(mǎn)足三個(gè)特性分別是 CO_NEWLOCALS,CO_OPTIMIZED 和 CO_NOFREE。
freevars & cellvars
我們使用下面的函數(shù)來(lái)對(duì)這兩個(gè)字段進(jìn)行分析:
def test_co02():
a = 1
b = 2
def g():
return a + b
return a + b + g()
上面的函數(shù)的信息如下所示(完整代碼見(jiàn)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ù)當(dāng)中會(huì)使用本地定義的變量,freevars 表示本地會(huì)使用其他函數(shù)定義的變量。
再來(lái)分析一下函數(shù) test_co02 的 flags,他的 flags 等于 0x3 因?yàn)橛虚]包的存在因此 flags 不會(huì)存在 CO_NOFREE,也就是少了值 0x0040 。
stacksize
這個(gè)字段存儲(chǔ)的是在函數(shù)在被虛擬機(jī)執(zhí)行的時(shí)候所需要的最大的??臻g的大小,這也是一種優(yōu)化手段,因?yàn)樵谥浪枰淖畲蟮臈?臻g,所以可以在函數(shù)執(zhí)行的時(shí)候直接分配指定大小的空間不需要在函數(shù)執(zhí)行的時(shí)候再去重新擴(kuò)容。
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ù)對(duì)應(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 # 下標(biāo)等于 0 的常量
1 # 下標(biāo)等于 1 的常量
2 # 下標(biāo)等于 2 的常量
names ()
varnames ('a', 'b')
freevars ()
cellvars ()我們現(xiàn)在來(lái)模擬一下執(zhí)行過(guò)程,在模擬之前我們首先來(lái)了解一下上面幾條字節(jié)碼的作用:
LOAD_CONST,將常量表當(dāng)中的下標(biāo)等于 i 個(gè)對(duì)象加載到棧當(dāng)中,對(duì)應(yīng)上面的代碼 LOAD_CONST 的參數(shù) i = 1。因此加載測(cè)常量等于 1 。因此現(xiàn)在??臻g如下所示:

STORE_FAST,將棧頂元素彈出并且保存到 co_varnames 對(duì)應(yīng)的下標(biāo)當(dāng)中,根據(jù)上面的字節(jié)碼參數(shù)等于 0 ,因此將 1 保存到 co_varnames[0] 對(duì)應(yīng)的對(duì)象當(dāng)中。

LOAD_CONST,將下標(biāo)等于 2 的常量加載進(jìn)入棧中。

STORE_FAST,將棧頂元素彈出,并且保存到 varnames 下標(biāo)為 1 的對(duì)象。

LOAD_FAST,是取出 co_varnames 對(duì)應(yīng)下標(biāo)的數(shù)據(jù),并且將其壓入棧中。我們直接連續(xù)執(zhí)行兩個(gè) LOAD_FAST 之后??臻g的布局如下:

BINARY_ADD,這個(gè)字節(jié)碼指令是將??臻g的兩個(gè)棧頂元素彈出,然后將兩個(gè)數(shù)據(jù)進(jìn)行相加操作,然后將相加得到的結(jié)果重新壓入棧中。

RETURN_VALUE,將棧頂元素彈出并且作為返回值返回。
從上面的整個(gè)執(zhí)行過(guò)程來(lái)看整個(gè)棧空間使用的最大的空間長(zhǎng)度為 2 ,因此 stacksize = 2 。
總結(jié)
在本篇文章當(dāng)中主要分析了一些 code obejct 當(dāng)中比較重要的字段,code object 是 cpython 虛擬機(jī)當(dāng)中一個(gè)比較重要的數(shù)據(jù)結(jié)構(gòu),深入的去理解這里面的字段對(duì)于我們理解 python 虛擬機(jī)非常有幫助。
以上就是深入理解Python虛擬機(jī)中的Code obejct的詳細(xì)內(nèi)容,更多關(guān)于Python虛擬機(jī)Code obejct的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 深入理解Python虛擬機(jī)中列表(list)的實(shí)現(xiàn)原理及源碼剖析
- 深入理解Python虛擬機(jī)中元組(tuple)的實(shí)現(xiàn)原理及源碼
- 深入理解Python虛擬機(jī)中浮點(diǎn)數(shù)(float)的實(shí)現(xiàn)原理及源碼
- 深入理解Python虛擬機(jī)中整型(int)的實(shí)現(xiàn)原理及源碼剖析
- 深入理解Python虛擬機(jī)中復(fù)數(shù)(complex)的實(shí)現(xiàn)原理及源碼剖析
- Python虛擬機(jī)棧幀對(duì)象及獲取源碼學(xué)習(xí)
- python虛擬機(jī)pyc文件結(jié)構(gòu)的深入理解
相關(guān)文章
Python?SQLAlchemy之SQL工具包和ORM的用法詳解
SQLAlchemy?是?Python?中一款非常流行的數(shù)據(jù)庫(kù)工具包,它對(duì)底層的數(shù)據(jù)庫(kù)操作提供了高層次的抽象,在本篇文章中,我們將介紹SQLAlchemy的兩個(gè)主要組成部分:SQL工具包和對(duì)象關(guān)系映射器的基本使用,需要的朋友可以參考下2023-08-08
Python實(shí)現(xiàn)的讀取/更改/寫(xiě)入xml文件操作示例
這篇文章主要介紹了Python實(shí)現(xiàn)的讀取/更改/寫(xiě)入xml文件操作,涉及Python針對(duì)xml文件的讀取、節(jié)點(diǎn)操作、寫(xiě)入等相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-08-08
Python判斷某個(gè)用戶(hù)對(duì)某個(gè)文件的權(quán)限
這篇文章主要為大家詳細(xì)介紹了Python如何判斷某個(gè)用戶(hù)對(duì)某個(gè)文件的權(quán)限,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
python中similarity函數(shù)實(shí)例用法
在本篇文章里小編給大家整理的是一篇關(guān)于python中similarity函數(shù)實(shí)例用法,有興趣的朋友們可以跟著學(xué)習(xí)下。2021-10-10
詳解python opencv圖像混合算術(shù)運(yùn)算
這篇文章主要介紹了python opencv圖像混合算術(shù)運(yùn)算的相關(guān)知識(shí),本文通過(guò)實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09
python sys.stdin和sys.stdout的用法說(shuō)明
這篇文章主要介紹了python sys.stdin和sys.stdout的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03
Python利用PyPDF2庫(kù)獲取PDF文件總頁(yè)碼實(shí)例
這篇文章主要介紹了Python利用PyPDF2庫(kù)獲取PDF文件總頁(yè)碼實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04
基于Python實(shí)現(xiàn)帕累托圖的示例詳解
帕累托圖是一種特殊的直方圖, 在項(xiàng)目管理知識(shí)體系中屬于質(zhì)量管理的工具。本文為大家整理了Python實(shí)現(xiàn)帕累托圖的方法,需要的可以參考一下2023-03-03
Python DataFrame實(shí)現(xiàn)固定周期內(nèi)統(tǒng)計(jì)每列的非零值
在數(shù)據(jù)處理中,使用DataFrame統(tǒng)計(jì)固定周期內(nèi)每列的非零值數(shù)量是一種常見(jiàn)需求,通過(guò)將數(shù)據(jù)分組并使用計(jì)數(shù)函數(shù),可以方便地實(shí)現(xiàn)此目標(biāo),具體方法包括首先計(jì)算每列的0值個(gè)數(shù),然后通過(guò)總數(shù)減去0值個(gè)數(shù)得到非零值的數(shù)量2024-09-09

