Python虚拟机中的函数机制
x86平台上函数调用发生时,系统会在运行时栈中创建新的栈帧,用于函数的执行
在Python中,PyFrameObject对象是对栈帧的模拟,Python虚拟机在执行函数调用时会动态地创建新的PyFrameObject对象。随着函数调用链的增长,这些PyFrameObject对象之间也会连接成一条PyFrameObject对象链。1.PyFunctionObject对象
在Python中,任何东西都是一个对象,函数也不例外函数是通过PyFunctionObject来实现的typedef struct { PyObject_HEAD PyObject *func_code; /* 对应函数编译后的PyCodeObject对象*/ PyObject *func_globals; /* 函数运行时的global名字空间爱你*/ PyObject *func_defaults; /* 默认参数(tuple或NULL)*/ PyObject *func_closure; /* Null or a tuple of cell objects,用于实现closure*/ PyObject *func_doc; /* 函数的文档(PyStringObject)*/ PyObject *func_name; /* 函数名称,函数的__name__属性,(PyStringObject)*/ PyObject *func_dict; /* 函数的__dict__属性(PyDictObject或NULL) */ PyObject *func_weakreflist; /* List of weak references */ PyObject *func_module; /* 函数的__module__,可以是任何对象 */ /* Invariant: * func_closure contains the bindings for func_code->co_freevars, so * PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code) * (func_closure may be NULL if PyCode_GetNumFree(func_code) == 0). */ } PyFunctionObject;
两个对象都和函数有关,PyCodeObject和PyFunctionObject。
PyCodeObject是编译时的结果PyCodeObject对象是对一段Python源代码的静态表示。源代码编译后,一个Code Block会产生一个且只有一个PyCodeObject这个PyCodeObject对象中包含了这个Code Block的一些静态的信息,所谓静态的信息是指可以从源代码中看到的信息。这些信息会分别存储在PyCodeObject的常量表co_consts,符号表co_names以及字节码序列co_code中,PyCodeObject是编译时的结果 PyFunctionObject对象是Python代码在运行时动态产生的,是在执行一个def语句的时候创建的。在PyFunctionObject中,包含的函数静态信息,存储在func_code中,func_code指向与函数代码对应的PyCodeObject对象。PyFunctionObject对象中包含了一些函数执行时必须的动态信息,即上下文信息。PyCodeObject------func_code-----PyFunctionObject
2.无参函数调用2.1 函数对象的创建func_0.py def f(): print "Function" f()
>>> co = compile(open('func_0.py').read(),'func_0.py','exec') >>> type(co)>>> type(co.co_consts[0]) >>> co.co_name ' ' >>> co.co_consts[0].co_name 'f' >>> import dis >>> import marsha1 Traceback (most recent call last): File " ", line 1, in ImportError: No module named marsha1 >>> import marshal >>> f = open('func_0.pyc','rb') >>> magic=f.read(4) >>> mtime=f.read(4) >>> >>> code=marshal.load(f) >>> dis.dis(code) 1 0 LOAD_CONST 0 ( ) 3 MAKE_FUNCTION 0 6 STORE_NAME 0 (f) 4 9 LOAD_NAME 0 (f) 12 CALL_FUNCTION 0 15 POP_TOP 16 LOAD_CONST 1 (None) 19 RETURN_VALUE >>>
Python实现时,把函数的声明与函数的实现分离
在调用一个函数之前,Python必须首先创建这个函数对象。函数对象的创建工作正式在def f()代码处完成的。def是函数的声明语句,从Python虚拟机的角度看,它其实是函数对象的创建语句。def-->MAKE_FUNCTION case MAKE_FUNCTION: v = POP(); /* 获得与函数f对应的PyCodeObject对象,在MAKE_FUNCTION之前,Python虚拟机会执行LOAD_CONST 0*/ x = PyFunction_New(v, f->f_globals); //v是PyCodeObject对象,而f_globals对象则是当前PyFrameObject对象中维护的global名字空间 Py_DECREF(v); /* XXX Maybe this should be a separate opcode? */ if (x != NULL && oparg > 0) { v = PyTuple_New(oparg); if (v == NULL) { Py_DECREF(x); x = NULL; break; } while (--oparg >= 0) { w = POP(); PyTuple_SET_ITEM(v, oparg, w); } err = PyFunction_SetDefaults(x, v); Py_DECREF(v); } PUSH(x); //新建的PyFunctionObject对象通过PUSH操作被压入到运行时栈中,随后的STORE_NAME和LOAD_NAME起作用 break; PyObject * PyFunction_New(PyObject *code, PyObject *globals) { PyFunctionObject *op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type); //申请PyFunctionObject对象所需的内存空间 static PyObject *__name__ = 0; if (op != NULL) { PyObject *doc; PyObject *consts; PyObject *module; //初始化PyFunctionObject对象中的各个域 op->func_weakreflist = NULL; Py_INCREF(code); op->func_code = code; //设置PyCodeObject对象 Py_INCREF(globals); op->func_globals = globals; //设置global名字空间 op->func_name = ((PyCodeObject *)code)->co_name; //设置函数名 Py_INCREF(op->func_name); op->func_defaults = NULL; /* No default arguments */ op->func_closure = NULL; consts = ((PyCodeObject *)code)->co_consts; //函数中的常量对象表 //函数的文档 if (PyTuple_Size(consts) >= 1) { doc = PyTuple_GetItem(consts, 0); if (!PyString_Check(doc) && !PyUnicode_Check(doc)) doc = Py_None; } else doc = Py_None; Py_INCREF(doc); op->func_doc = doc; op->func_dict = NULL; op->func_module = NULL; /* __module__: If module name is in globals, use it. Otherwise, use None. */ if (!__name__) { __name__ = PyString_InternFromString("__name__"); if (!__name__) { Py_DECREF(op); return NULL; } } module = PyDict_GetItem(globals, __name__); if (module) { Py_INCREF(module); op->func_module = module; } } else return NULL; _PyObject_GC_TRACK(op); return (PyObject *)op; }
2.2 函数调用
CALL_FUNCTION指令开始,Python虚拟机进入了函数调用动作:case CALL_FUNCTION: { PyObject **sp; PCALL(PCALL_ALL); sp = stack_pointer; //获得当前的运行时栈栈顶指针 #ifdef WITH_TSC x = call_function(&sp, oparg, &intr0, &intr1); #else x = call_function(&sp, oparg); #endif stack_pointer = sp; PUSH(x); if (x != NULL) continue; break; } static PyObject * call_function(PyObject ***pp_stack, int oparg #ifdef WITH_TSC , uint64* pintr0, uint64* pintr1 #endif ) { //处理函数参数信息 int na = oparg & 0xff; //位置参数的个数 int nk = (oparg>>8) & 0xff; //键参数的个数 int n = na + 2 * nk; //获得PyFunctionObject对象 PyObject **pfunc = (*pp_stack) - n - 1; PyObject *func = *pfunc; PyObject *x, *w; /* Always dispatch PyCFunction first, because these are presumed to be the most frequent callable object. */ if (PyCFunction_Check(func) && nk == 0) { int flags = PyCFunction_GET_FLAGS(func); PyThreadState *tstate = PyThreadState_GET(); PCALL(PCALL_CFUNCTION); if (flags & (METH_NOARGS | METH_O)) { PyCFunction meth = PyCFunction_GET_FUNCTION(func); PyObject *self = PyCFunction_GET_SELF(func); if (flags & METH_NOARGS && na == 0) { C_TRACE(x, (*meth)(self,NULL)); } else if (flags & METH_O && na == 1) { PyObject *arg = EXT_POP(*pp_stack); C_TRACE(x, (*meth)(self,arg)); Py_DECREF(arg); } else { err_args(func, flags, na); x = NULL; } } else { PyObject *callargs; callargs = load_args(pp_stack, na); READ_TIMESTAMP(*pintr0); C_TRACE(x, PyCFunction_Call(func,callargs,NULL)); READ_TIMESTAMP(*pintr1); Py_XDECREF(callargs); } } else { //对PyFunctionObject对象进行调用 if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) { /* optimize access to bound methods */ PyObject *self = PyMethod_GET_SELF(func); PCALL(PCALL_METHOD); PCALL(PCALL_BOUND_METHOD); Py_INCREF(self); func = PyMethod_GET_FUNCTION(func); Py_INCREF(func); Py_DECREF(*pfunc); *pfunc = self; na++; n++; } else Py_INCREF(func); READ_TIMESTAMP(*pintr0); if (PyFunction_Check(func)) x = fast_function(func, pp_stack, n, na, nk); //n指明了在运行时栈中,栈顶的多少个元素是与参数相关的 else x = do_call(func, pp_stack, na, nk); READ_TIMESTAMP(*pintr1); Py_DECREF(func); } /* Clear the stack of the function object. Also removes the arguments in case they weren't consumed already (fast_function() and err_args() leave them on the stack). */ //pp_stack就是在CALL_FUNCTION的指令代码中传入的当前运行时栈的栈顶指针 while ((*pp_stack) > pfunc) { w = EXT_POP(*pp_stack); Py_DECREF(w); PCALL(PCALL_POP); } return x; }
无参函数进入fast_function之后,最终会进入PyEval_EvalFrameEx,Python虚拟机在一个新的PyFrameObject(栈帧)环境中开始一次执行新的字节码
指令序列的循环,这个新的字节码指令序列正式函数所对应的字节码指令序列。无参函数进入快速通道3.函数执行时的名字空间
在执行LOAD_NAME指令时,Python虚拟机会以此从三个PyDictObject对象中进行搜索,搜索的顺序是:f_locals、f_globals、f_builtins。通过globals传递,才使得函数可以使用函数外的符号在一开始执行py文件时,它的f_locals和f_globals指向同一个PyDictObject对象
C语言中函数是否可调用(可编译通过)完全是基于源代码中函数出现的位置做的分析
Python依靠的是运行时的名字空间 4.函数参数的实现4.1 参数类别位置参数(positioanal argument):f(a,b) a、b被称为位置参数键参数(key argument):f(a,b,name='Python') name='Python'被称为键参数扩展位置参数(excess positional argument):def f(a,b,*list), *list被称为扩展位置参数扩展键参数(excess key argument):def f(a,b,**keys),**key被称为扩展键参数Python虚拟机开始执行CALL_FUNCTION指令时,会首先获得一个指令参数oparg。oparg,记录着函数参数的个数信息
CALL_FUNCTION指令参数的长度是两个字节,在低字节,记录着位置参数的个数,在高字节,记录着键参数的个数两个与参数有关的信息:co_argcount和co_nlocals
在Python中,函数参数和函数的局部变量关系非常密切,在某种意义上,函数就是一种函数局部变量,它们在内存中是连续放置的。当Python需要为函数申请存放局部变量的内存空间时,就需要通过co_nlocals知道局部变量的总数。通过co_argcount告诉Python虚拟机函数一共有多少个参数 由于位置参数会导致一条LOAD_CONST指令,而键参数会导致两条LOAD_CONST指令,n=na+2*nk5.函数中局部变量的访问
局部变量和函数参数一样,都在f_localsplus中运行时栈前面的那段内存空间中。6.嵌套函数、闭包与decorator
base = 1 def get_compare(base): def real_compare(value): return value > base return real_compare compare_with_10 = get_compare(10) print compare_with_10(5) print compare_with_10(20)
名字空间与函数捆绑后的结果被称为一个闭包(closure)
这一部分不是很理解,下次再接着看