python函數(shù)調(diào)用棧幀 函數(shù)棧幀的調(diào)用過程

python中的frame是什么意思?

Frame對象表示執(zhí)行幀,表示程序運行時函數(shù)調(diào)用棧中的某一幀。

創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于做網(wǎng)站、網(wǎng)站設(shè)計、關(guān)嶺網(wǎng)絡(luò)推廣、微信小程序、關(guān)嶺網(wǎng)絡(luò)營銷、關(guān)嶺企業(yè)策劃、關(guān)嶺品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎;創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供關(guān)嶺建站搭建服務(wù),24小時服務(wù)熱線:13518219792,官方網(wǎng)址:www.muchs.cn

想要獲得某個函數(shù)相關(guān)的棧幀,則必須在調(diào)用這個函數(shù)且這個函數(shù)尚未返回時獲取??梢允褂胹ys模塊的_getframe()函數(shù)、或inspect模塊的currentframe()函數(shù)獲取當(dāng)前棧幀。

f_back: 調(diào)用棧的前一幀。

f_code: 棧幀對應(yīng)的code對象。

f_locals: 用在當(dāng)前棧幀時與內(nèi)建函數(shù)locals()相同,但你可以先獲取其他幀然后使用這個屬性獲取那個幀的locals()。

f_globals: 用在當(dāng)前棧幀時與內(nèi)建函數(shù)globals()相同,但你可以先獲取其他幀……

示例: 假設(shè)在下面代碼的第四行打斷點

函數(shù)被斷點停止住時刻的frame信息如下

更多Python知識請關(guān)注Python視頻教程欄目。

python 為什么要進(jìn)行遞歸限制

因為遞歸的效率較低,如果不進(jìn)行限制可能運行一個py文件會花費大量的時間

ue5python原理

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)信息,但沒有包含程序運行時的動態(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ī)器上的運行,X86的運行時棧幀如下圖

Python解釋器的原理就是模擬上述行為。當(dāng)發(fā)生函數(shù)調(diào)用時,創(chuàng)建新的棧幀,對應(yīng)Python的實現(xiàn)就是PyFrameObject對象。

PyFrameObject對象創(chuàng)建程序運行時的動態(tài)信息,即執(zhí)行環(huán)境

7.1 PyFrameObject

typedef struct _frame{

PyObject_VAR_HEAD //"運行時棧"的大小是不確定的

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; //"運行時棧"的棧底位置

PyObject **f_stacktop; //"運行時棧"的棧頂位置

//...

int f_lasti; //上一條字節(jié)碼指令在f_code中的偏移位置

int f_lineno; //當(dāng)前字節(jié)碼對應(yīng)的源代碼行

//...

//動態(tài)內(nèi)存,維護(hù)(局部變量+cell對象集合+free對象集合+運行時棧)所需要的空間

PyObject *f_localsplus[1];

} PyFrameObject;

每一個 PyFrameObject對象都維護(hù)了一個 PyCodeObject對象,這表明每一個 PyFrameObject中的動態(tài)內(nèi)存空間對象都和源代碼中的一段Code相對應(yīng)。

關(guān)于Python中的一段為Python腳本添加行號腳本

C語言有__LINE__來表示源代碼的當(dāng)前行號,經(jīng)常在記錄日志時使用。Python如何獲取源代碼的當(dāng)前行號?

The C Language has the __LINE__ macro, which is wildly used in logging, presenting the current line of the source file. And how to get the current line of a Python source file?

exception輸出的函數(shù)調(diào)用棧就是個典型的應(yīng)用:

A typical example is the output of function call stack when an exception:

python代碼

File "D:\workspace\Python\src\lang\lineno.py", line 19, in module

afunc()

File "D:\workspace\Python\src\lang\lineno.py", line 15, in afunc

errmsg = 1/0

ZeroDivisionError: integer division or modulo by zero

那么我們就從錯誤棧的輸出入手,traceback模塊中:

Now that, Let's begin with the output of an exception call stack, in the traceback module:

python代碼

def print_stack(f=None, limit=None, file=None):

"""Print a stack trace from its invocation point.

The optional 'f' argument can be used to specify an alternate

stack frame at which to start. The optional 'limit' and 'file'

arguments have the same meaning as for print_exception().

"""

if f is None:

try:

raise ZeroDivisionError

except ZeroDivisionError:

f = sys.exc_info()[2].tb_frame.f_back

print_list(extract_stack(f, limit), file)

def print_list(extracted_list, file=None):

"""Print the list of tuples as returned by extract_tb() or

extract_stack() as a formatted stack trace to the given file."""

if file is None:

file = sys.stderr

for filename, lineno, name, line in extracted_list:

_print(file,

' File "%s", line %d, in %s' % (filename,lineno,name))

if line:

_print(file, ' %s' % line.strip())

traceback模塊構(gòu)造一個ZeroDivisionError,并通過sys模塊的exc_info()來獲取運行時上下文。我們看到,所有的秘密都在tb_frame中,這是函數(shù)調(diào)用棧中的一個幀。

traceback constructs an ZeroDivisionError, and then call the exc_info() of the sys module to get runtime context. There, all the secrets hide in the tb_frame, this is a frame of the function call stack.

對,就是這么簡單!只要我們能找到調(diào)用棧frame對象即可獲取到行號!因此,我們可以用同樣的方法來達(dá)到目的,我們自定義一個lineno函數(shù):

Yes, It's so easy! If only a frame object we get, we can get the line number! So we can have a similar implemetation to get what we want, defining a function named lineno:

python代碼

import sys

def lineno():

frame = None

try:

raise ZeroDivisionError

except ZeroDivisionError:

frame = sys.exc_info()[2].tb_frame.f_back

return frame.f_lineno

def afunc():

# if error

print "I have a problem! And here is at Line: %s"%lineno()

是否有更方便的方法獲取到frame對象?當(dāng)然有!

Is there any other way, perhaps more convinient, to get a frame object? Of course YES!

python代碼

def afunc():

# if error

print "I have a proble! And here is at Line: %s"%sys._getframe().f_lineno

類似地,通過frame對象,我們還可以獲取到當(dāng)前文件、當(dāng)前函數(shù)等信息,就像C語音的__FILE__與__FUNCTION__一樣。其實現(xiàn)方式,留給你們自己去發(fā)現(xiàn)。

Thanks to the frame object, similarly, we can also get current file and current function name, just like the __FILE__ and __FUNCTION__ macros in C. Debug the frame object, you will get the solutions.

python堆和棧的區(qū)別有哪些

堆(Heap)與棧(Stack)是開發(fā)人員必須面對的兩個概念,在理解這兩個概念時,需要放到具體的場景下,因為不同場景下,堆與棧代表不同的含義。一般情況下,有兩層含義:

(1)程序內(nèi)存布局場景下,堆與棧表示的是兩種內(nèi)存管理方式;

(2)數(shù)據(jù)結(jié)構(gòu)場景下,堆與棧表示兩種常用的數(shù)據(jù)結(jié)構(gòu)。

相關(guān)推薦:《Python教程》

堆與棧實際上是操作系統(tǒng)對進(jìn)程占用的內(nèi)存空間的兩種管理方式,主要有如下幾種區(qū)別:

(1)管理方式不同。棧由操作系統(tǒng)自動分配釋放,無需我們手動控制;堆的申請和釋放工作由程序員控制,容易產(chǎn)生內(nèi)存泄漏;

(2)空間大小不同。每個進(jìn)程擁有的棧的大小要遠(yuǎn)遠(yuǎn)小于堆的大小。理論上,程序員可申請的堆大小為虛擬內(nèi)存的大小,進(jìn)程棧的大小 64bits 的 Windows 默認(rèn) 1MB,64bits 的 Linux 默認(rèn) 10MB;

(3)生長方向不同。堆的生長方向向上,內(nèi)存地址由低到高;棧的生長方向向下,內(nèi)存地址由高到低。

(4)分配方式不同。堆都是動態(tài)分配的,沒有靜態(tài)分配的堆。棧有2種分配方式:靜態(tài)分配和動態(tài)分配。靜態(tài)分配是由操作系統(tǒng)完成的,比如局部變量的分配。動態(tài)分配由alloca函數(shù)進(jìn)行分配,但是棧的動態(tài)分配和堆是不同的,他的動態(tài)分配是由操作系統(tǒng)進(jìn)行釋放,無需我們手工實現(xiàn)。

(5)分配效率不同。棧由操作系統(tǒng)自動分配,會在硬件層級對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行,這就決定了棧的效率比較高。堆則是由C/C++提供的庫函數(shù)或運算符來完成申請與管理,實現(xiàn)機(jī)制較為復(fù)雜,頻繁的內(nèi)存申請容易產(chǎn)生內(nèi)存碎片。顯然,堆的效率比棧要低得多。

(6)存放內(nèi)容不同。棧存放的內(nèi)容,函數(shù)返回地址、相關(guān)參數(shù)、局部變量和寄存器內(nèi)容等。當(dāng)主函數(shù)調(diào)用另外一個函數(shù)的時候,要對當(dāng)前函數(shù)執(zhí)行斷點進(jìn)行保存,需要使用棧來實現(xiàn),首先入棧的是主函數(shù)下一條語句的地址,即擴(kuò)展指針寄存器的內(nèi)容(EIP),然后是當(dāng)前棧幀的底部地址,即擴(kuò)展基址指針寄存器內(nèi)容(EBP),再然后是被調(diào)函數(shù)的實參等,一般情況下是按照從右向左的順序入棧,之后是被調(diào)函數(shù)的局部變量,注意靜態(tài)變量是存放在數(shù)據(jù)段或者BSS段,是不入棧的。出棧的順序正好相反,最終棧頂指向主函數(shù)下一條語句的地址,主程序又從該地址開始執(zhí)行。堆,一般情況堆頂使用一個字節(jié)的空間來存放堆的大小,而堆中具體存放內(nèi)容是由程序員來填充的。

從以上可以看到,堆和棧相比,由于大量malloc()/free()或new/delete的使用,容易造成大量的內(nèi)存碎片,并且可能引發(fā)用戶態(tài)和核心態(tài)的切換,效率較低。棧相比于堆,在程序中應(yīng)用較為廣泛,最常見的是函數(shù)的調(diào)用過程由棧來實現(xiàn),函數(shù)返回地址、EBP、實參和局部變量都采用棧的方式存放。雖然棧有眾多的好處,但是由于和堆相比不是那么靈活,有時候分配大量的內(nèi)存空間,主要還是用堆。

無論是堆還是棧,在內(nèi)存使用時都要防止非法越界,越界導(dǎo)致的非法內(nèi)存訪問可能會摧毀程序的堆、棧數(shù)據(jù),輕則導(dǎo)致程序運行處于不確定狀態(tài),獲取不到預(yù)期結(jié)果,重則導(dǎo)致程序異常崩潰,這些都是我們編程時與內(nèi)存打交道時應(yīng)該注意的問題。

新聞名稱:python函數(shù)調(diào)用棧幀 函數(shù)棧幀的調(diào)用過程
網(wǎng)頁路徑:http://www.muchs.cn/article20/hggsco.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計、定制開發(fā)、外貿(mào)建站、網(wǎng)站建設(shè)、云服務(wù)器品牌網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)