欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python虛擬機棧幀對象及獲取源碼學習

 更新時間:2023年03月23日 17:05:08   作者:Blanker  
這篇文章主要為大家介紹了Python虛擬機棧幀對象及獲取源碼學習,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

Python虛擬機

注:本篇是根據教程學習記錄的筆記,部分內容與教程是相同的,因為轉載需要填鏈接,但是沒有,所以填的原創(chuàng),如果侵權會直接刪除。此外,本篇內容大部分都咨詢了ChatGPT,為筆者解決了很多問題。

問題:

Python 程序執(zhí)行過程與字節(jié)碼中,我們研究了Python程序的編譯過程:通過Python解釋器中的編譯器對 Python 源碼進行編譯,最終獲得代碼對象 PyCodeObject 。編譯器根據語法規(guī)則對源碼進行作用域的劃分,并以此為單位來編譯源碼,最終為每個作用域生成一個代碼對象。代碼對象則保存了字節(jié)碼,以及相關名字、常量等靜態(tài)上下文信息。

(上面這段話是原文章的作者總結的,我個人覺得還是很到位的,大家也可以再回顧一下這篇筆記的內容: Python 程序執(zhí)行過程與字節(jié)碼,更深刻體會下。)

那么當我們得到了編譯產出的代碼對象后,虛擬機是如何解析并執(zhí)行其中的字節(jié)碼指令的呢?與語法作用域相對應的運行時名字空間,在虛擬機中又是如何動態(tài)維護的呢?

1. 棧幀對象

1.1 PyFrameObject

  • 當 Python 解釋器加載一個模塊或者執(zhí)行函數時,會為對應的 PyCodeObject 創(chuàng)建一個 PyFrameObject 對象,并將其壓入 Python 解釋器的執(zhí)行棧中。以函數為例,PyFrameObject 對象表示函數調用的棧幀對象,它包含了函數調用時的所有狀態(tài)信息,包括局部變量、棧、當前指令等信息。

具體地我們來看一下執(zhí)行上下文的具體結構——PyFrameObject,源碼如下:

typedef struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */
    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;
    int f_lasti;                /* Last instruction if called */
    /* Call PyFrame_GetLineNumber() instead of reading this field
       directly.  As of 2.3 f_lineno is only valid when tracing is
       active (i.e. when f_trace is set).  At other times we use
       PyCode_Addr2Line to calculate the line from the current
       bytecode index. */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
} PyFrameObject;

源碼分析(只列出重要字段):

思考:PyFrameObject為什么沒有記錄閉包信息?

  • f_back:表示當前棧幀的前一個棧幀,即調用當前函數的函數的棧幀。Python解釋器使用這個字段來實現(xiàn)函數調用的遞歸和返回。如果當前函數是最外層函數,即沒有調用它的函數,則該字段為NULL。
  • f_code:表示當前棧幀對應的 PyCodeObject 對象,即當前函數的字節(jié)碼和相關信息。Python 解釋器使用這個字段來執(zhí)行函數中的字節(jié)碼指令。
  • f_builtins:表示當前棧幀的內建變量字典,即當前函數中訪問的所有內建函數和對象的名稱和值。Python 解釋器使用這個字段來實現(xiàn)對內建函數和對象的訪問。
  • f_locals:表示當前棧幀的局部變量字典,即當前函數的所有局部變量的名稱和值。Python 解釋器使用這個字段來實現(xiàn)變量的讀取和寫入操作。
  • f_lasti:表示當前棧幀執(zhí)行的最后一條指令的指令碼在字節(jié)碼序列中的索引。Python 解釋器使用這個字段來記錄當前函數執(zhí)行的進度,以便在函數被中斷或者函數返回時,能夠恢復到正確的執(zhí)行位置。
  • f_lineno:表示當前棧幀執(zhí)行的源代碼行號。Python 解釋器使用這個字段來跟蹤當前函數的行號,以便在發(fā)生異常時能夠提供更準確的錯誤信息。
  • f_localsplus:表示當前棧幀的棧頂指針,即當前函數調用的棧的頂部。Python 解釋器使用這個字段來實現(xiàn)函數調用的參數傳遞和返回值傳遞。
  • PyFrameObject 對象本身不記錄閉包相關的信息是出于設計上的考慮。一個主要的原因是為了保持執(zhí)行棧的簡潔性和高效性。
  • 閉包是一種在 Python 中廣泛使用的編程模式,但是它在實現(xiàn)上是比較復雜的。在解釋器執(zhí)行 Python 代碼時,一個函數在定義時可能沒有引用外部變量,但是在運行時卻可能引用了。因此,如果要記錄函數中使用的外部變量,就需要在運行時動態(tài)地創(chuàng)建一個閉包對象,并將其與函數對象關聯(lián)起來。這就會給執(zhí)行棧的實現(xiàn)帶來很大的復雜性。
  • 另一個原因是,閉包可能會被頻繁地創(chuàng)建和銷毀,而在執(zhí)行棧中保存大量的閉包信息會導致執(zhí)行效率變慢,甚至可能引起內存泄漏。因此,Python 解釋器在設計執(zhí)行棧時,選擇不記錄閉包相關的信息,以保持執(zhí)行棧的簡潔性和高效性。
  • 雖然 PyFrameObject 對象本身不記錄閉包相關的信息,但是 Python 解釋器可以通過其他方式來獲取函數的閉包信息,例如通過函數對象的 closure 屬性。

PyFrameObject結構圖如下:

  • 其中,f_code字段保存了當前執(zhí)行的代碼對象,最核心的字節(jié)碼就在代碼對象中。而f_lasti字段則保存著上條已執(zhí)行字節(jié)碼的編號。虛擬機內部用一個C局部變量next_instr維護下條字節(jié)碼的位置,并據此加載下一條待執(zhí)行的字節(jié)碼指令,原理和CPU的指令指針寄存器(%rip)一樣。
  • 另外,注意到f_back字段執(zhí)行前一個棧幀對象,也就是調用者的棧幀對象。這樣一來,棧幀對象按照調用關系串成一個調用鏈。(這里和x86CPU棧幀布局是如出一轍的,原作者在這里介紹了x86CPU棧幀布局與函數調用之間的關系,筆者能力有限就不介紹了,大家感興趣的可以自行查找相關資料(主要還是微機原理和匯編學的不是很好。。。))

1.2 棧幀對象鏈

現(xiàn)在,我們以具體例子來考察Python棧幀對象鏈以及函數調用之間的關系:

pi = 3.14
def square(r):
    return r ** 2
def circle_area(r):
    return pi * square(r)
def main():
    print(circle_area(5))
if __name__ == '__main__':
    main()

當Python開始執(zhí)行這個程序時,虛擬機先創(chuàng)建一個棧幀對象,用于執(zhí)行模塊代碼對象:

當虛擬機執(zhí)行到模塊代碼第13行時,發(fā)生了函數調用。這時,虛擬機會新建一個棧幀對象,并開始執(zhí)行函數main()的代碼對象:

隨著函數調用逐層深入,當調用square()函數時,調用鏈達到最長:

當函數調用完畢后,虛擬機通過f_back字段找到前一個棧幀對象并回到調用者代碼中繼續(xù)執(zhí)行。

1.3 棧幀獲取

棧幀對象PyFrameObject中保存著Python運行時信息,在底層執(zhí)行流控制以及程序調試中非常有用。在Python代碼層面,我們可以通過sys模塊中的_getframe()函數,即可獲得當前棧幀對象:

>>> import sys
>>> frame = sys._getframe()
>>> frame
<frame at 0x00000183FA78F870, file '<pyshell#1>', line 1, code <module>>
>>> dir(frame)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'f_back', 'f_builtins', 'f_code', 'f_globals', 'f_lasti', 'f_lineno', 'f_locals', 'f_trace', 'f_trace_lines', 'f_trace_opcodes']

拿到棧幀對象之后,我們來具體看一下相關的屬性值,以之前的求面積的函數為例:

>>> import sys
>>> pi = 3.14
>>> def square(r):
        frame = sys._getframe()
        while frame:
            print('name:', frame.f_code.co_name)
            print('Locals', list(frame.f_locals.keys()))
            print('Globals', list(frame.f_globals.keys()))
            print('===========')
            frame = frame.f_back
        return r ** 2
>>> def circle_area(r):
        return pi * square(r)
>>> def main():
        print(circle_area(2))
>>> if __name__ == '__main__':
        main()
name: square
Locals ['r', 'frame']
Globals ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'sys', 'pi', 'square', 'circle_area', 'main']
===========
name: circle_area
Locals ['r']
Globals ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'sys', 'pi', 'square', 'circle_area', 'main']
===========
name: main
Locals []
Globals ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'sys', 'pi', 'square', 'circle_area', 'main']
===========
name: <module>
Locals ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'sys', 'pi', 'square', 'circle_area', 'main']
Globals ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'sys', 'pi', 'square', 'circle_area', 'main']
===========
12.56

小拓展:自定義函數實現(xiàn)sys._getframe()功能:(這里是原作者舉的一個例子,個人感覺對相關知識的理解是有幫助的)

當Python程序拋出異常時,會將執(zhí)行上下文帶出來,保存在異常中:

>>> try:
        1 / 0
    except Exception as e:
        print(e.__traceback__.tb_frame)
<frame at 0x000002440D95BC50, file '<pyshell#5>', line 4, code <module>>

因此,我們可以自定義一個getframe()函數:

>>> def getframe():
        try:
            1 / 0
        except Exception as e:
            return e.__traceback__.tb_frame.f_back

注意:getframe()中通過異常獲得的是自己的棧幀對象e.traceback.tb_frame,所以還需要通過f_back字段找到調用者的棧幀。

2. 字節(jié)碼執(zhí)行

Python 虛擬機執(zhí)行代碼對象的主要函數有兩個:

PyEval_EvalCodeEx() 是通用接口,一般用于函數這樣帶參數的執(zhí)行場景:

PyObject *
PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
                  PyObject *const *args, int argcount,
                  PyObject *const *kws, int kwcount,
                  PyObject *const *defs, int defcount,
                  PyObject *kwdefs, PyObject *closure);

PyEval_EvalCode() 是更高層封裝,用于模塊等無參數的執(zhí)行場景:

PyObject *
PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals);

這兩個函數最終調用 _PyEval_EvalCodeWithName() 函數,初始化棧幀對象并調用 PyEval_EvalFrame 系列函數進行處理。棧幀對象將貫穿代碼對象執(zhí)行的始終,負責維護執(zhí)行時所需的一切上下文信息。而PyEval_EvalFrame 系列函數最終調用 _PyEval_EvalFrameDefault() 函數,虛擬機執(zhí)行的核心就在這里(具體源碼這里就不講解了)。

PyObject *
PyEval_EvalFrame(PyFrameObject *f);
PyObject *
PyEval_EvalFrameEx(PyFrameObject *f, int throwflag);
PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag);

文章后續(xù)以順序執(zhí)行、if判斷、while循環(huán)詳細講解了字節(jié)碼的執(zhí)行過程,這里筆者就不贅述了。

以上就是Python虛擬機棧幀對象及獲取源碼學習的詳細內容,更多關于Python虛擬機棧幀對象獲取的資料請關注腳本之家其它相關文章!

相關文章

  • 解析django的csrf跨站請求偽造

    解析django的csrf跨站請求偽造

    本文主要介紹了解析django的csrf跨站請求偽造,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-08-08
  • Ubuntu下升級 python3.7.1流程備忘(推薦)

    Ubuntu下升級 python3.7.1流程備忘(推薦)

    這篇文章主要介紹了Ubuntu下升級 python3.7.1流程備忘,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-12-12
  • python matplotlib餅狀圖參數及用法解析

    python matplotlib餅狀圖參數及用法解析

    這篇文章主要介紹了python matplotlib餅狀圖參數及用法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11
  • python實現(xiàn)的簡單文本類游戲實例

    python實現(xiàn)的簡單文本類游戲實例

    這篇文章主要介紹了python實現(xiàn)的簡單文本類游戲,以兩個實例形式分析了python操作文本與字符串的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-04-04
  • pandas?dataframe獲取所有行名稱與列名稱方法示例

    pandas?dataframe獲取所有行名稱與列名稱方法示例

    這篇文章主要給大家介紹了關于pandas?dataframe獲取所有行名稱與列名稱的相關資料,Pandas是Python中用于數據分析的非常重要的庫,它提供了多種方法來獲取列名,需要的朋友可以參考下
    2023-09-09
  • 基于進程內通訊的python聊天室實現(xiàn)方法

    基于進程內通訊的python聊天室實現(xiàn)方法

    這篇文章主要介紹了基于進程內通訊的python聊天室實現(xiàn)方法,實例分析了Python聊天室的相關實現(xiàn)技巧,需要的朋友可以參考下
    2015-06-06
  • Jupyter notebook 輸出部分顯示不全的解決方案

    Jupyter notebook 輸出部分顯示不全的解決方案

    這篇文章主要介紹了Jupyter notebook 輸出部分顯示不全的解決方案,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Python 如何求矩陣的逆

    Python 如何求矩陣的逆

    這篇文章主要介紹了Python 如何求矩陣的逆案例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • 使用numpy nonzero 找出非0元素

    使用numpy nonzero 找出非0元素

    這篇文章主要介紹了使用numpy nonzero 找出非0元素的方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-05-05
  • python本地文件服務器實例教程

    python本地文件服務器實例教程

    這篇文章主要給大家介紹了關于python本地文件服務器的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-05-05

最新評論