Python原始碼閱讀-閉包的實現

wklken發表於2015-12-06

閉包

e.g.

需要回答, 什麼是閉包, CPython底層是如何實現的?

PyCodeObject

我們關注兩個, co_freevarsco_cellvars

對於我們上面的那個示例, add是外層函式, do_add是巢狀函式, 我們可以通過func_code列印看看

此時圖示

這時候, 只是記錄了使用到的變數名, 標記下是否使用了外層的/被內層使用的變數

具體的值是在執行時確定的, 例如

此時x=5, 這個是在add的名字空間裡面的, 那麼, x=5是怎麼傳遞到巢狀函式內? 巢狀函式又是如何知曉x的值?

記住這兩個問題, 然後我們首先來看一個新的資料結構

PyCellObject

這是個很簡單的基本物件, 有一個ob_ref指向另一個PyObject, 僅此而已

圖示

作用呢?

值的確認與傳遞過程

呼叫

此時, 開始呼叫函式

邏輯即, 如果發現當前函式co_cellvars非空, 即表示存在被內層函式呼叫的變數, 那麼遍歷這個co_cellvars集合, 拿到集合中每個變數名在當前名字空間中的值, 然後放到當前函式的f->f_localsplus中.

這裡, 我們可以知道x=5被放進去了

為什麼放到f->f_localsplus中呢?

看看PyFrameObject

注意f_localsplus

建立過程

call_function的時候, new了一個PyFrameObject

原因: 因為函式中的區域性變數總是固定不變的, 在編譯時就能確定區域性變數使用的記憶體空間的位置, 也能確定訪問區域性變數的位元組碼應該如何訪問記憶體, 有了這些資訊, Python就能借助靜態的方法實現區域性變數, 而不是動態查詢PyDictObject, 提高執行效率

示例函式的f_localsplus

看一下上面賦值用的巨集定義

最終得到

接下去呢? CALL_FUNCTION最後怎麼處理將cell傳入巢狀函式?

傳遞

CALL_FUNCTION 完成new一個PyFrameObject之後,

最終執行這個frame

PyEval_EvalFrameEx

檢視一下dis的結果

首先LOAD_CLOSURE 0

然後, BUILD_TUPLE, 將cell物件打包成tuple, 得到('x', )

然後, 開始, 載入巢狀函式do_add, 入棧

呼叫MAKE_CLOSURE

來關注一下 PyFunction_SetClosure

do_addPyFunctionObjectfunc_closure指向一個tuple

注意: 這時候, 外層變數已經固定下來了!!!!!!

然後, 在巢狀函式被呼叫的時候

看下PyFunction_GET_CLOSURE

然後, 進入 PyEval_EvalCodeEx, 注意這裡的closure引數即上一步取出來的func_closure, 即外層函式傳進來的tuple

最後, 再來看一個閉包的dis

注意BUILD_TUPLE

dis結果

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

Python原始碼閱讀-閉包的實現 Python原始碼閱讀-閉包的實現

相關文章