中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python虛擬機棧幀對象及獲取的方法是什么

發布時間:2023-03-25 16:47:01 來源:億速云 閱讀:154 作者:iii 欄目:開發技術

本篇內容主要講解“Python虛擬機棧幀對象及獲取的方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Python虛擬機棧幀對象及獲取的方法是什么”吧!

    Python虛擬機

    問題:

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

    1. 棧幀對象

    1.1 PyFrameObject

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

    具體地我們來看一下執行上下文的具體結構——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解釋器使用這個字段來實現函數調用的遞歸和返回。如果當前函數是最外層函數,即沒有調用它的函數,則該字段為NULL。

    • f_code:表示當前棧幀對應的 PyCodeObject 對象,即當前函數的字節碼和相關信息。Python 解釋器使用這個字段來執行函數中的字節碼指令。

    • f_builtins:表示當前棧幀的內建變量字典,即當前函數中訪問的所有內建函數和對象的名稱和值。Python 解釋器使用這個字段來實現對內建函數和對象的訪問。

    • f_locals:表示當前棧幀的局部變量字典,即當前函數的所有局部變量的名稱和值。Python 解釋器使用這個字段來實現變量的讀取和寫入操作。

    • f_lasti:表示當前棧幀執行的最后一條指令的指令碼在字節碼序列中的索引。Python 解釋器使用這個字段來記錄當前函數執行的進度,以便在函數被中斷或者函數返回時,能夠恢復到正確的執行位置。

    • f_lineno:表示當前棧幀執行的源代碼行號。Python 解釋器使用這個字段來跟蹤當前函數的行號,以便在發生異常時能夠提供更準確的錯誤信息。

    • f_localsplus:表示當前棧幀的棧頂指針,即當前函數調用的棧的頂部。Python 解釋器使用這個字段來實現函數調用的參數傳遞和返回值傳遞。

    • PyFrameObject 對象本身不記錄閉包相關的信息是出于設計上的考慮。一個主要的原因是為了保持執行棧的簡潔性和高效性。

    • 閉包是一種在 Python 中廣泛使用的編程模式,但是它在實現上是比較復雜的。在解釋器執行 Python 代碼時,一個函數在定義時可能沒有引用外部變量,但是在運行時卻可能引用了。因此,如果要記錄函數中使用的外部變量,就需要在運行時動態地創建一個閉包對象,并將其與函數對象關聯起來。這就會給執行棧的實現帶來很大的復雜性。

    • 另一個原因是,閉包可能會被頻繁地創建和銷毀,而在執行棧中保存大量的閉包信息會導致執行效率變慢,甚至可能引起內存泄漏。因此,Python 解釋器在設計執行棧時,選擇不記錄閉包相關的信息,以保持執行棧的簡潔性和高效性。

    • 雖然 PyFrameObject 對象本身不記錄閉包相關的信息,但是 Python 解釋器可以通過其他方式來獲取函數的閉包信息,例如通過函數對象的 closure 屬性。

    PyFrameObject結構圖如下:

    Python虛擬機棧幀對象及獲取的方法是什么

    • 其中,f_code字段保存了當前執行的代碼對象,最核心的字節碼就在代碼對象中。而f_lasti字段則保存著上條已執行字節碼的編號。虛擬機內部用一個C局部變量next_instr維護下條字節碼的位置,并據此加載下一條待執行的字節碼指令,原理和CPU的指令指針寄存器(%rip)一樣。

    • 另外,注意到f_back字段執行前一個棧幀對象,也就是調用者的棧幀對象。這樣一來,棧幀對象按照調用關系串成一個調用鏈。

    1.2 棧幀對象鏈

    現在,我們以具體例子來考察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開始執行這個程序時,虛擬機先創建一個棧幀對象,用于執行模塊代碼對象:

    Python虛擬機棧幀對象及獲取的方法是什么

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

    Python虛擬機棧幀對象及獲取的方法是什么

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

    Python虛擬機棧幀對象及獲取的方法是什么

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

    1.3 棧幀獲取

    棧幀對象PyFrameObject中保存著Python運行時信息,在底層執行流控制以及程序調試中非常有用。在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

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

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

    >>> 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. 字節碼執行

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

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

    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() 是更高層封裝,用于模塊等無參數的執行場景:

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

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

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

    到此,相信大家對“Python虛擬機棧幀對象及獲取的方法是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    永寿县| 西畴县| 龙陵县| 绩溪县| 贺兰县| 惠州市| 金川县| 多伦县| 上虞市| 绩溪县| 塔城市| 仙桃市| 界首市| 龙泉市| 英山县| 陇川县| 黄龙县| 南康市| 巴彦淖尔市| 武隆县| 哈尔滨市| 城市| 新疆| 唐河县| 望城县| 通城县| 安西县| 乐至县| 紫阳县| 清丰县| 普格县| 巫溪县| 石首市| 新蔡县| 青浦区| 闻喜县| 集贤县| 黑龙江省| 鞍山市| 通州市| 阜城县|