1. 程式人生 > >從函數內函數定義看python的函數實現

從函數內函數定義看python的函數實現

指向 UNC 私有 指令集 生成 mes eth 額外 局部變量

一、問題

在C/C++中,函數的定義本質上是在編譯階段完成,而函數調用是由鏈接完成。但是對於python這種語言,函數的定義和調用都是由解釋器在運行時完成,或者說,解釋器在執行函數定義的時候,同樣是生成了虛擬機指令,這個指令通常可以理解為MAKE_FUNCTION這個虛擬機指令,這個指令生成的則是一個PyFunctionObject對象。對應的,在函數調用的時候執行的是CALL_FUNCTION,這個指令需要生成一個PyFrameObject對象。

二、CALL_FUNCTION的執行

1、函數的調用

一個函數的構成。靜態代碼信息大致對應函數模版,也就是函數本身的描述信息:例如函數的虛擬機指令集合、使用的常量、使用的自由變量數量、局部變量數量等信息。但是,函數執行時候,最為關鍵的是需要一個棧信息,這個是函數的動態特性。如果一個函數需要在多線程中運行,那麽它必須有自己私有的堆棧信息。
可以看到,在執行CALL_FUNCTION指令時,會動態創建一個PyFrameObject對象,這個變量從名字上看就可以知道它對於保存局部變量有著責無旁貸的義務。
static PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
PyObject **args, Py_ssize_t argcount,
PyObject **kwnames, PyObject **kwargs,
Py_ssize_t kwcount, int kwstep,
PyObject **defs, Py_ssize_t defcount,
PyObject *kwdefs, PyObject *closure,
PyObject *name, PyObject *qualname)
{
……
assert(tstate != NULL);
f = PyFrame_New(tstate, co, globals, locals);
……
}

2、堆棧的生成

那麽一個函數運行時堆棧中使用的臨時變量存儲在哪裏呢?
在調用PyObject_GC_NewVar的時候,額外要求在PyFrameObject結構之後分配extras個Object對象,這個extras包含了堆棧大小,局部變量、cell變量和free變量,一個函數的運行時信息就存儲在這裏,它的基地址由f_localsplus執行。從實現上看,f_localsplus定義為
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
它定義在函數的最後,從而對象創建之後,這個變量就自動指向了額外變量
PyFrameObject *
PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
PyObject *locals)
{
……
Py_ssize_t extras, ncells, nfrees;
ncells = PyTuple_GET_SIZE(code->co_cellvars);
nfrees = PyTuple_GET_SIZE(code->co_freevars);
extras = code->co_stacksize + code->co_nlocals + ncells +
nfrees;
if (free_list == NULL) {
f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type,
extras);
……
f->f_code = code;
extras = code->co_nlocals + ncells + nfrees;
f->f_valuestack = f->f_localsplus + extras;
for (i=0; i<extras; i++)
f->f_localsplus[i] = NULL;
f->f_locals = NULL;
f->f_trace = NULL;
f->f_exc_type = f->f_exc_value = f->f_exc_traceback = NULL;

……
}

三、一個推論

既然函數定義也有對應的機器指令,那麽如果一個函數中嵌套了另一個函數定義,在執行外層函數的時候,內層函數的定義依然會被執行。並且由於兩次執行的棧幀不同,所以每次執行都會由MAKE_FUNCTION生成新的函數對象。我們可以通過下面的代碼驗證一下,可以看到返回的兩個內部函數定義並不相同,雖然邏輯上看是相同的函數。
tsecer@harry: cat innerdef.py
def outter():
def inner():
print("something")
return inner

inner1 =outter()
inner2 = outter()

print(inner1, inner2)

tsecer@harry: python innerdef.py
(<function inner at 0x7f6e6b5015f0>, <function inner at 0x7f6e6b501668>)
tsecer@harry:

從函數內函數定義看python的函數實現