隨著人工智能時代的來臨,python成為了人們學(xué)習(xí)編程的首先語言。那么,python程序怎么運(yùn)行的?我們下面來介紹下。
創(chuàng)新互聯(lián)專注于成都做網(wǎng)站、網(wǎng)站制作、成都外貿(mào)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計、網(wǎng)站制作、網(wǎng)站開發(fā)。公司秉持“客戶至上,用心服務(wù)”的宗旨,從客戶的利益和觀點(diǎn)出發(fā),讓客戶在網(wǎng)絡(luò)營銷中找到自己的駐足之地。尊重和關(guān)懷每一位客戶,用嚴(yán)謹(jǐn)?shù)膽B(tài)度對待客戶,用專業(yè)的服務(wù)創(chuàng)造價值,成為客戶值得信賴的朋友,為客戶解除后顧之憂。
python程序執(zhí)行原理
我們都知道,使用C,C++之類的編譯性語言編寫的程序,是需要從源文件轉(zhuǎn)換成計算機(jī)使用的機(jī)器語言,經(jīng)過鏈接器鏈接之后形成了二進(jìn)制可執(zhí)行文件。運(yùn)行該程序的時候,就可以二進(jìn)制程序從硬盤載入到內(nèi)存中并運(yùn)行。
相關(guān)推薦:《Python教程》
但是對于Python而言,Python源碼不需要編譯成二進(jìn)制代碼,它可以直接從源代碼運(yùn)行程序。Python解釋器將源代碼轉(zhuǎn)換為字節(jié)碼,然后把編譯好的字節(jié)碼轉(zhuǎn)發(fā)到Python虛擬機(jī)(PVM)中進(jìn)行執(zhí)行。當(dāng)我們運(yùn)行Python程序的時候,Python解釋器會執(zhí)行兩個步驟。
(1) 把源代碼編譯成字節(jié)碼。編譯后的字節(jié)碼是特定于Python的一種表現(xiàn)形式,它不是二進(jìn)制的機(jī)器碼,需要進(jìn)一步編譯才能被機(jī)器執(zhí)行,這也是Python代碼無法運(yùn)行的像C,C++一樣快的原因。如果Python進(jìn)程在機(jī)器上擁有寫入權(quán)限,那么它將把程序的字節(jié)碼保存為一個以.pyc為擴(kuò)展名的文件,如果Python無法在機(jī)器上寫入字節(jié)碼,那么宇節(jié)碼將會在內(nèi)存中生成并在程序結(jié)束時自動丟棄。在構(gòu)建程序的時候最好給Python賦上在計算機(jī)上寫的權(quán)限,這樣只要源代碼沒有改變,生成的.pyc文件可以重復(fù)利用,提高執(zhí)行效率。
(2) 把編譯好的字節(jié)碼轉(zhuǎn)發(fā)到Python虛擬機(jī)(PVM)中進(jìn)行執(zhí)行。PVM是Python Virtual Machine的簡稱,它是Python的運(yùn)行引擎,是Python系統(tǒng)的一部分,它是迭代運(yùn)行字節(jié)碼指令的一個大循環(huán),一個接一個地完成操作。
Python先把代碼(.py文件)編譯成字節(jié)碼,交給字節(jié)碼虛擬機(jī),然后解釋器一條一條執(zhí)行字節(jié)碼指令,從而完成程序的執(zhí)行。
1.1python先把代碼(.py文件)編譯成字節(jié)碼,交給字節(jié)碼虛擬機(jī),然后解釋器會從編譯得到的PyCodeObject對象中一條一條執(zhí)行字節(jié)碼指令,
并在當(dāng)前的上下文環(huán)境中執(zhí)行這條字節(jié)碼指令,從而完成程序的執(zhí)行。Python解釋器實際上是在模擬操作中執(zhí)行文件的過程。PyCodeObject對象
中包含了字節(jié)碼指令以及程序的所有靜態(tài)信息,但沒有包含程序運(yùn)行時的動態(tài)信息——執(zhí)行環(huán)境(PyFrameObject)
2. 字節(jié)碼
字節(jié)碼在python解釋器程序里對應(yīng)的是PyCodeObject對象
.pyc文件是字節(jié)碼在磁盤上的表現(xiàn)形式
2.1從整體上看:OS中執(zhí)行程序離不開兩個概念:進(jìn)程和線程。python中模擬了這兩個概念,模擬進(jìn)程和線程的分別是PyInterpreterState和
PyTreadState。即:每個PyThreadState都對應(yīng)著一個幀棧,python解釋器在多個線程上切換。當(dāng)python解釋器開始執(zhí)行時,它會先進(jìn)行一
些初始化操作,最后進(jìn)入PyEval_EvalFramEx函數(shù),它的作用是不斷讀取編譯好的字節(jié)碼,并一條一條執(zhí)行,類似CPU執(zhí)行指令的過程。函數(shù)內(nèi)部
主要是一個switch結(jié)構(gòu),根據(jù)字節(jié)碼的不同執(zhí)行不同的代碼。
3. .pyc文件
PyCodeObject對象的創(chuàng)建時機(jī)是模塊加載的時候,及import
Python test.py會對test.py進(jìn)行編譯成字節(jié)碼并解釋執(zhí)行,但是不會生成test.pyc
如果test.py加載了其他模塊,如import urlib2, Python會對urlib2.py進(jìn)行編譯成字節(jié)碼,生成urlib2.pyc,然后對字節(jié)碼進(jìn)行解釋
如果想生成test.pyc,我們可以使用Python內(nèi)置模塊py_compile來編譯。
加載模塊時,如果同時存在.py和pyc,Python會嘗試使用.pyc,如果.pyc的編譯時間早于.py的修改時間,則重新編譯.py并更新.pyc。
4. PyCodeObject
Python代碼的編譯結(jié)果就是PyCodeObject對象
typedef struct {
PyObject_HEAD
int co_argcount; /* 位置參數(shù)個數(shù) */
int co_nlocals; /* 局部變量個數(shù) */
int co_stacksize; /* 棧大小 */
int co_flags;
PyObject *co_code; /* 字節(jié)碼指令序列 */
PyObject *co_consts; /* 所有常量集合 */
PyObject *co_names; /* 所有符號名稱集合 */
PyObject *co_varnames; /* 局部變量名稱集合 */
PyObject *co_freevars; /* 閉包用的的變量名集合 */
PyObject *co_cellvars; /* 內(nèi)部嵌套函數(shù)引用的變量名集合 */
/* The rest doesn’t count for hash/cmp */
PyObject *co_filename; /* 代碼所在文件名 */
PyObject *co_name; /* 模塊名|函數(shù)名|類名 */
int co_firstlineno; /* 代碼塊在文件中的起始行號 */
PyObject *co_lnotab; /* 字節(jié)碼指令和行號的對應(yīng)關(guān)系 */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
} PyCodeObject;
5. .pyc文件格式
加載模塊時,模塊對應(yīng)的PyCodeObject對象被寫入.pyc文件
6.分析字節(jié)碼
6.1解析PyCodeObject
Python提供了內(nèi)置函數(shù)compile可以編譯python代碼和查看PyCodeObject對象
6.2指令序列co_code的格式
opcode oparg opcode opcode oparg …
1 byte 2 bytes 1 byte 1 byte 2 bytes
Python內(nèi)置的dis模塊可以解析co_code
7. 執(zhí)行字節(jié)碼
Python解釋器的原理就是模擬可執(zhí)行程序再X86機(jī)器上的運(yùn)行,X86的運(yùn)行時棧幀如下圖
Python解釋器的原理就是模擬上述行為。當(dāng)發(fā)生函數(shù)調(diào)用時,創(chuàng)建新的棧幀,對應(yīng)Python的實現(xiàn)就是PyFrameObject對象。
PyFrameObject對象創(chuàng)建程序運(yùn)行時的動態(tài)信息,即執(zhí)行環(huán)境
7.1 PyFrameObject
typedef struct _frame{
PyObject_VAR_HEAD //"運(yùn)行時棧"的大小是不確定的
struct _frame *f_back; //執(zhí)行環(huán)境鏈上的前一個frame,很多個PyFrameObject連接起來形成執(zhí)行環(huán)境鏈表
PyCodeObject *f_code; //PyCodeObject 對象,這個frame就是這個PyCodeObject對象的上下文環(huán)境
PyObject *f_builtins; //builtin名字空間
PyObject *f_globals; //global名字空間
PyObject *f_locals; //local名字空間
PyObject **f_valuestack; //"運(yùn)行時棧"的棧底位置
PyObject **f_stacktop; //"運(yùn)行時棧"的棧頂位置
//...
int f_lasti; //上一條字節(jié)碼指令在f_code中的偏移位置
int f_lineno; //當(dāng)前字節(jié)碼對應(yīng)的源代碼行
//...
//動態(tài)內(nèi)存,維護(hù)(局部變量+cell對象集合+free對象集合+運(yùn)行時棧)所需要的空間
PyObject *f_localsplus[1];
} PyFrameObject;
每一個 PyFrameObject對象都維護(hù)了一個 PyCodeObject對象,這表明每一個 PyFrameObject中的動態(tài)內(nèi)存空間對象都和源代碼中的一段Code相對應(yīng)。
對于可變參數(shù)默認(rèn)是引用傳值, 但是不能去修改它的指向, 一旦修改就是按值傳遞.
#?coding=utf-8
def?f(a):
a?=?[0]
print(a)
if?__name__?==?'__main__':
a?=?[1,?2,?3]
f(a)
print(a)
上面的代碼對a重新賦值, 試圖改變a的指向, 那么這時的a就是一個新的局部變量, 而非全局變量a
像a[0] = 100, a.append(0)的操作不會觸發(fā)上述規(guī)則, 和你的輸出一樣