python嵌套函數、閉包與decorator


1

一段代碼的執行結果不光取決與代碼中的符號,更多地是取決於代碼中符號的意義,而運行時的意義是由名字空間決定的。名字空間是在運行時由python虛擬機動態維護的,但是有時候我們希望能將名字空間靜態化。即:我們希望有的代碼不受名字空間變換的影響,始終保持一致的行為和結果。

這樣的意義何在呢?

這就不得不說說嵌套函數了。

上面代碼中,我們只設置了一次基准值。此后,在每次進行比較操作的時候,盡管調用的實際函數real_compare的local名字空間中沒有base,而global名字空間中有base = 1,但是函數調用結果顯示,real_compare以一種神奇的方式得知了base應該是10,而不是1。

也就是說在real_compare函數作為返回值被傳遞給compare_with_10的時候,有一個名字空間已經與real_compare緊緊地綁定在一起了,在執行real_compare的時候,這個名字空間又被恢復了,這就是一種將名字空間靜態化的方法。

這個名字空間和函數捆綁后的結果被稱為一個閉包(closure)。

比如PyFunctionObject是Python虛擬機專門為字節碼指令准備的大包袱,global名字空間、默認參數都能在PyFunctionObject中與字節碼指令捆綁在一起,所以PyFunctionObject也是一個Python中閉包的具體表現。

閉包是最內嵌套的一種實現。

 

不用閉包我們也能實現上面相同的功能,

上面這個就不是閉包,為什么呢?

因為最后一個print語句中傳入的base=1的情況,居然能夠改變基准值,這里如果是閉包的話,那就會拋出異常的。

我們利用函數默認值,居然實現了閉包的效果。

那么,閉包和默認參數的實現方式是不是相似的呢?

1.1實現閉包的基石

閉包的創建通常是利用嵌套函數來完成的。在PyCodeObject中,與嵌套函數相關的屬性有co_cellvars 、co_freevars。前者是一個保存嵌套的作用域中使用的變量的集合,后者是保存使用了外層作用區域中的變量的集合,兩者均為tuple。

PyFrameObject對象中,也有與閉包實現相關的屬性,這就是f_localsplus,在PyFrame_New中,extras = code->co_stacksize + code->co_nlocals+ncells+ nfrees;

extras正是f_localsplus指向的那片內存的大小。

PyFunctionObject中,還有一個與閉包實現有關的屬性。

 

1.2閉包實現

1.2.1創建closure

在PyCodeObject的co_cellvars中有東西,在PyEval_EvalCodeEx中,Python虛擬機就會如同處理默認參數一樣,將co_cellvars中的東西拷貝到新創建的PyFrameObject的f_localsplus中。

python虛擬機先獲得被內層嵌套函數引用的符號名,然后就創建一個cell對象,隨后cell對象被拷貝到新創建的PyFrameObject的f_localsplus中。位置為co->co_nlocals+i,說明在f_localsplus中,cell對象的位置是在局部變量之后的。

上面總結就是:把PyCodeObject中的co_cellvars寫道PyFrameObject的f_localsplus.

處理了cell對象之后,Python虛擬機進入PyEval_EvalFrameEx,從而正式開始函數調用。

從PyFrame中取得f_localsplus中的cell對象,存為freevars對象。在PyFunctionObject中,存儲需要傳遞的內容。

2.2使用closure

閉包是在外層函數中創建,在內層函數中使用。

在將內層函數賦值給一個名字之后,例如show_value,

然后在show_vale()的時候,發現內層函數對應的PyCodeObject中的co_flags中包含了CO_NESTED,所以會進入到PyEval_EvalCodeEx.

而在內層函數對應的PyCodeObject中co_freevars里有引用外層函數中的符號名字,在PyEval_EvalCodeEx中,就會對這個co_freevars進行處理。

即在PyFunctionObject對象中與PyCodeObject對象綁定的裝滿了PyCellObject對象的tuple,所以這里其實偶是將PyCellObject對象一個一個地放入到f_localsplus中相應的位置。

處理完之后,PyFrameObject中就有閉包需要的元素了。

這里和調用外層函數時一致的,在內層函數調用的過程中,當引用外層作用域符號時,一定是到f_localsplus中的free變量區域中獲取得到符號對應的值。

到這里我們已經講完closure的創建到傳遞到使用的全過程了。

2.3 decorator

裝飾器的本質其實就是閉包。

其他沒啥好說的了,只是包裝了一下而已。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM