<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
注:本篇是根據教學學習記錄的筆記,部分內容與教學是相同的,因為轉載需要填連結,但是沒有,所以填的原創,如果侵權會直接刪除。此外,本篇內容大部分都諮詢了ChatGPT,為筆者解決了很多問題。
問題:
在 Python 程式執行過程與位元組碼中,我們研究了Python程式的編譯過程:通過Python直譯器中的編譯器對 Python 原始碼進行編譯,最終獲得程式碼物件 PyCodeObject 。編譯器根據語法規則對原始碼進行作用域的劃分,並以此為單位來編譯原始碼,最終為每個作用域生成一個程式碼物件。程式碼物件則儲存了位元組碼,以及相關名字、常數等靜態上下文資訊。
(上面這段話是原文章的作者總結的,我個人覺得還是很到位的,大家也可以再回顧一下這篇筆記的內容: Python 程式執行過程與位元組碼,更深刻體會下。)
那麼當我們得到了編譯產出的程式碼物件後,虛擬機器器是如何解析並執行其中的位元組碼指令的呢?與語法作用域相對應的執行時名稱空間,在虛擬機器器中又是如何動態維護的呢?
具體地我們來看一下執行上下文的具體結構——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為什麼沒有記錄閉包資訊?
PyFrameObject結構圖如下:
現在,我們以具體例子來考察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開始執行這個程式時,虛擬機器器先建立一個棧幀物件,用於執行模組程式碼物件:
當虛擬機器器執行到模組程式碼第13行時,發生了函數呼叫。這時,虛擬機器器會新建一個棧幀物件,並開始執行函數main()的程式碼物件:
隨著函數呼叫逐層深入,當呼叫square()函數時,呼叫鏈達到最長:
當函數呼叫完畢後,虛擬機器器通過f_back欄位找到前一個棧幀物件並回到呼叫者程式碼中繼續執行。
棧幀物件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欄位找到呼叫者的棧幀。
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);
文章後續以順序執行、if判斷、while迴圈詳細講解了位元組碼的執行過程,這裡筆者就不贅述了。
以上就是Python虛擬機器器棧幀物件及獲取原始碼學習的詳細內容,更多關於Python虛擬機器器棧幀物件獲取的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45