Python源码——虚拟机函数机制

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 'code'>
>>> type(co.co_consts[0])
<type 'code'>
>>> co.co_name
'<module>'
>>> co.co_consts[0].co_name
'f'

>>> import dis
>>> import marsha1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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 (<code object f at 00AF6728, file "C:
\Documents and Settings\happy\func_0.py", line 1>)
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*nk

5.函数中局部变量的访问
局部变量和函数参数一样,都在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)

这一部分不是很理解,下次再接着看

原文地址:https://www.cnblogs.com/moonflow/p/2368449.html