码迷,mamicode.com
首页 > 编程语言 > 详细

《python源码剖析》笔记 python虚拟机中的函数机制

时间:2014-06-24 21:56:10      阅读:415      评论:0      收藏:0      [点我收藏+]

标签:style   class   blog   code   http   tar   

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie


1.Python虚拟机在执行函数调用时会动态地创建新的 PyFrameObject对象,
这些PyFrameObject对象之间会形成PyFrameObject对象链,模拟x86平台上运行时栈


2.PyFuctionObject对象

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;
	PyObject *func_module; //函数的__module__,可以是任何对象
} PyFunctionObject;

函数的声明和函数的实现是分离在不同的PyCodeObject中的
def f()创建了函数对象
bubuko.com,布布扣


PYCodeObject:一个Code Block的静态信息。比如a = 1,符号a和值1以及它们的联系是一种静态信息,分别存储在
PyCodeObject的常量表co_consts,符号表co_names以及字节码序列co_code中
PyFunctionObject:包括函数的静态信息,由func_code指向函数代码对应的PyCodeObject对象,还包含了函数在执行时
的动态信息,如func_globals。


名字空间里键值对是动态信息,必须在运行时动态创建


一段代码只能对应一个PyCodeObject,却可以对应多个PyFunctionObject。
bubuko.com,布布扣

3.无参函数调用
??图11-2,图11-3 为什么用这两个 strRef,internStr
def f():
#LOAD_CONST 0
#MAKE_FUNCTION 0
#STORE_NAME 0
	print "Function"
	#LOAD_CONST 1
	#PRINT_ITEM
	#PRINT_NEWLINE
	#LOAD_CONST
	#RETURN_VALUE
f()
#LOAD_NAME 0
#CALL_FUNCTION 0
#POP_TOP
#LOAD_CONST 1
#RETURN_VALUE



def f()创建了函数对象,如图11-6所示 

bubuko.com,布布扣

 call_function函数调用

static PyObject *
call_function(PyObject ***pp_stack, int oparg
#ifdef WITH_TSC
                , uint64* pintr0, uint64* pintr1
#endif
                )
{
	//[1]:处理函数参数信息
    int na = oparg & 0xff; //位置参数、扩展位置参数个数
    int nk = (oparg>>8) & 0xff; //键参数、扩展键参数个数
    int n = na + 2 * nk; //总共的参数个数 nk*2是因为(键,值)对中键和值各占一个位置
    //[2]:获得PyFunctionObject对象
	PyObject **pfunc = (*pp_stack) - n - 1; 
    PyObject *func = *pfunc; 
    PyObject *x, *w;


    if (PyCFunction_Check(func) && nk == 0) {
		//...
		
    } else {
        if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
        
        } else
       //[3]:对PyFunctionObject对象进行调用
	   if (PyFunction_Check(func))
            x = fast_function(func, pp_stack, n, na, nk);
        else
            x = do_call(func, pp_stack, na, nk);
        //...
    }
	//...
    return x;
}

fast_function
对于一般函数(除Python独有的函数(如Draw(x, *key, **dict))外的函数),PyEval_EvalFrameEx,进入一个新的PyFrameObject(栈桢)环境中开始执行新的字节码指令序列的循环
另一条路径,PyEval_EvalCodeEx
static PyObject *
fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
{
    PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
    PyObject *globals = PyFunction_GET_GLOBALS(func);
    PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
    PyObject **d = NULL;
    int nd = 0;


    //...
	//[1]:一般函数的快速通道
    if (argdefs == NULL && co->co_argcount == n && nk==0 &&
        co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
        PyFrameObject *f;
        PyObject *retval = NULL;
        PyThreadState *tstate = PyThreadState_GET();
        PyObject **fastlocals, **stack;
        int i;


        f = PyFrame_New(tstate, co, globals, NULL);
        //...
		retval = PyEval_EvalFrameEx(f,0);
        //...
		return retval;
    }
    if (argdefs != NULL) {
        d = &PyTuple_GET_ITEM(argdefs, 0);
        nd = Py_SIZE(argdefs);
    }
    return PyEval_EvalCodeEx(co, globals,
                             (PyObject *)NULL, (*pp_stack)-n, na,
                             (*pp_stack)-2*nk, nk, d, nd,
                             PyFunction_GET_CLOSURE(func));
}

4.函数执行时的名字空间
LOAD_NAME会依次在f_locals, f_globals, f_builtins搜索符号
在执行func_0.py的字节码指令序列时的global名字空间和执行函数f的字节码指令序列时的global名字空间实际上是同一个名字空间
bubuko.com,布布扣


5.
参数类型:位置参数、键参数、扩展位置参数、扩展键参数

          def f(  a,     n = "Python",       *list,               **key)
扩展位置参数、扩展键参数用局部变量实现
oparg:参数个数,只需记录位置参数和键参数的个数,扩展位置参数和扩展键参数是特殊的位置参数和键参数
oparg有两个字节,低字节-->位置参数的个数,高字节-->键参数的个数
记录参数需要的内存数:n = na + 2*nk,因为位置参数只需一条LOAD_CCONST,而键参数由于(键,值)对需要两条LOAD_CONST
一个参数是位置参数还是键参数由实参决定,非键参数必须在键参数之前,eg.
def fun(a, b):
	pass
fun(1, b = 2)

bubuko.com,布布扣

bubuko.com,布布扣

bubuko.com,布布扣

bubuko.com,布布扣


5.位置参数的传递
def f(name, age):
#LOAD_CONST 0
#MAKE_FUNCTION 0
#STORE_NAME 0
	age += 5
	#LOAD_FAST 1
	#LOAD_CONST 5
	#INPLACE_ADD
	#STORE_FAST 1
	print "[", name, ",", age, "]"
	#LOAD_CONST 2
	#PRINT_ITEM
	#LOAD_FAST 0
	#PRINT_ITEM
	#LOAD_CONST 3
	#PRINT_ITEM
	#LOAD_FAST 1
	#PRINT_ITEM
	#LOAD_CONST 4
	#PRINT_ITEM
	#PRINT_NEWLINE
	#LOAD_CONST 0
	#RETURN_VALUE
age = 5;
print age
f("Robert", age)
#LOAD_NAME 0
#LOAD_CONST 2
#LOAD_NAME 1
#CALL_FUNCTION 2
#POP_TOP
print age
call_function --> fast_function
bubuko.com,布布扣

位置参数的访问
LOAD_FAST i, 将f_localsplus[i]中的对象到运行时栈中
STORE_FAST i, 将运行时栈顶中的值存放到f_localsplus[i]中
在调用函数时,Python将函数参数值从左到右到运行时栈中,在fast_function中,又将这些参数
依次拷贝到新建的与函数对应的PyFrameObject对象的f_localsplus中,最终的效果就是,Python虚拟机将
函数调用时传入的参数从左到右地依次存放在新建的PyFrameObject对象的f_localsplus中。
在访问函数参数时,Python虚拟机没有按照通常访问符号的做法,去查什么名字空间,而是直接通过
一个索引(偏移位置)来访问f_localsplus中存储的符号对应的值对象。
bubuko.com,布布扣

位置参数的默认值
def f(a = 1, b = 2):
#LOAD_CONST 0
#LOAD_CONST 1
#LOAD_CONST 2
#MAKE_FUNCTION 2
#STORE_NAME 0
	print a + b
	#LOAD_FAST 0
	#LOAD_FAST 1
	#BINARY_ADD
	#PRINT_ITEM
	#PRINT_NEWLINE
	#LOAD_CONST 0
	#RETURN_VALUE
f()
#LOAD_NAME 0
#CALL_FUNCTION 0
#POP_TOP
f(b=3)
#LOAD_NAME 0
#LOAD_CONST 3
#LOAD_CONST 4
#CALL_FUNCTION 256
#POP_TOP
#LOAD_CONST 5
#RETURN_VALUE

无论函数有无参数,其def语句编译后的结果都是一样的,差别是在进行函数调用的时候产生的,
无参函数在调用前公将PyFunctionObject对象压入运行时栈,而带参函数还需将参数也压入运行时栈
而有默认参数值的函数的def语句编译后还会多出几条LOAD_CONST字节码,将默认值压入栈中,
然后在MAKE_FUNCTION中,将这些默认值全部存放到一个PyTupleObject对象中,再将该对象设置为
PyFrameObject.func_defaults的值。这样,函数参数的默认值也成为了PyFunctionObject对象的一部分。
总结:PyFunctionObject对象包括三个主要的内容,PyCodeObject, globals名字空间和func_defaults

PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
           PyObject **args, int argcount, PyObject **kws, int kwcount,
           PyObject **defs, int defcount, PyObject *closure)
{
    register PyFrameObject *f;
    register PyObject *retval = NULL;
    register PyObject **fastlocals, **freevars;
    PyThreadState *tstate = PyThreadState_GET();
    PyObject *x, *u;
	//[1]:创建PyFrameObject对象
    f = PyFrame_New(tstate, co, globals, locals);
    
    fastlocals = f->f_localsplus;
    freevars = f->f_localsplus + co->co_nlocals;
	
	//[a]:遍历键参数,确定函数的def语句中是否出现了键参数的名字
	for (i = 0; i < kwcount; i++) {
		PyObject *keyword = kws[2*i];
		PyObject *value = kws[2*i + 1];
		int j;
		//[b]:在函数的变量名表中寻找keyword
		//...
		for (j = 0; j < co->co_argcount; j++) {
			PyObject *nm = co_varnames[j];
			int cmp = PyObject_RichCompareBool(
				keyword, nm, Py_EQ);
			if (cmp > 0)
				goto kw_found;
			else if (cmp < 0)
				goto fail;
		}
		if (kwdict == NULL) {
			PyObject *kwd_str = kwd_as_string(keyword);
			if (kwd_str) {
				PyErr_Format(PyExc_TypeError,
							 "%.200s() got an unexpected "
							 "keyword argument '%.400s'",
							 PyString_AsString(co->co_name),
							 PyString_AsString(kwd_str));
				Py_DECREF(kwd_str);
			}
			goto fail;
		}
		PyDict_SetItem(kwdict, keyword, value);
		continue;
	  kw_found:
		if (GETLOCAL(j) != NULL) {
			goto fail;
		}
		Py_INCREF(value);
		SETLOCAL(j, value);
	}
	
    if (co->co_argcount > 0 ||
        co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)) {
        int i;
        int n = argcount;
        PyObject *kwdict = NULL;
        //n为CALL_FUNCTION的参数指示的传入的位置参数个数,即na,这里为0
		
		//...
		
		//[2]:判断是否使用参数的默认值
		if (argcount < co->co_argcount) {
            //m 一般位置参数的个数
			int m = co->co_argcount - defcount;
			//[3]:函数调用者必须传递一般位置参数的参数值
            for (i = argcount; i < m; i++) {
                if (GETLOCAL(i) == NULL) {
                    goto fail;
                }
            }
			//[4]:n > m意味着调用者希望替换一些默认位置参数的默认值
            if (n > m)
                i = n - m;
            else
                i = 0;
			//[5]:设置默认位置参数的默认值
            for (; i < defcount; i++) {
                if (GETLOCAL(m+i) == NULL) {
                    PyObject *def = defs[i];
                    Py_INCREF(def);
                    SETLOCAL(m+i, def);
                }
            }
        }
    }
    
    retval = PyEval_EvalFrameEx(f,0);
    return retval;
}


当最终需要设置默认值的参数个数确定之后,Python虚拟机会从PyFrameObject对象的func_defaults中将这些参数取出,
并通过SETLOCAL将其放入PyFrameObject对象的f_localsplus所管理的内存块中。


在编译时,Python会将函数的def语句中出现的参数的名称都记录在变量名表(co_varnames)中。


对于第二次调用
Python虚拟机会先在PyCodeObject对象中的co_varnames中查找‘b‘,得到它对应的序号,然后通过SETLOCAL将
新建的PyFrameObject对象中的f_localsplus中参数b对应的位置设置为3。接下来再为需要设置默认值的默认位置参数
设置默认值。


扩展位置参数和扩展键参数,是作为局部变量来实现的
*list是由PyTupleObject实现,而**key是由PyDictObject对象实现
在编译一个函数时,如果发现了*list这样的扩展位置参数形式,会在PyCodeObject对象的co_flags中添加标识符号:CO_VARARGS,
如果发现**key扩展键参数的形式,会向co_flags中添加 CO_VARKEYWORDS

bubuko.com,布布扣

PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
           PyObject **args, int argcount, PyObject **kws, int kwcount,
           PyObject **defs, int defcount, PyObject *closure)
{
	register PyFrameObject *f;
	register PyObject **fastlocals, **freevars;
	PyThreadState *tstate = PyThreadState_GET();
	PyObject *x, *u;
	//创建PyFrameObject对象
	f = PyFrame_New(tstate, co, globals, locals);
	fastlocals = f->localsplus;
	freevars = f->f_localsplus + f->f_nlocals;
	//[1]:判断是否需要处理扩展位置参数或扩展键参数
	if(co->co_argcount > 0 || co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)){
		int i;
		int n = argcount;
		
		if(argcount > co->co_argcount ){
			n = co->co_argcount;
		}
		//[2]:设置位置参数的参数值
		for(i = 0; i < n; i++){
			x = args[i];
			SETLOCAL(i, x);
		}
		//[3]:处理扩展位置参数
		if(co->co_flags & CO_VARARGS){
			//[4]:将PyTupleObject对象放入到f_localsplus中
			u = PyTuple_New(argcount - n );
			SETLOCAL(co->co_argcount, u);
			//[5]:将扩展位置参数放入到PyTupleObject中
			for( i = n; i < argcount; i++){
				x = args[i];
				PyTuple_SET_ITEM(u, i - n, x);
			}
		}
	}
	
}


Python虚拟机会在函数的PyCodeObject对象的变量名对象表(co_varnames)中查找键参数的名字,只有在查找失败时,才能
确定该键参数应该属于一个扩展键参数。
PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
           PyObject **args, int argcount, PyObject **kws, int kwcount,
           PyObject **defs, int defcount, PyObject *closure)
{
	//...
	if(co->co_flags > 0 || co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)){
		int i;
		int n = argcount;
		PyObject *kwdict = NULL;
		//...
		//[1]:创建PyDictObject对象,并将其放到f_localsplus中
		if(co->co_flags & CO_VARKEYWORDS){
			kwdict = PyDict_New();
			i = co->co_argcount;
			//[2]:PyDictObject对象必须在PyTupleObject之后
			if(co->co_flags & CO_VARARGS)
				i++;
			SETLOCAL(i, kwdict);
		}
		//遍历键参数,确定函数的def语句中是否出现了键参数的名字
		for(i = 0; i < kwcount; i++){
			PyObject *keyword = kws[2 * i];
			PyObject *value = kws[2 * i + 1];
			int j;
			//在函数的变量名表中寻找keyword
			for(j = 0; j < co->co_argcount; j++){
				PyObject *nm = PyTuple_GET_ITEM(co->co_varnames, j);
				int cmp = PyObject_RichCompareBool(keyword, nm, Py_EQ);
				if(cmp > 0)//在co_varnames中找到keyword
					break;
				else if (cmp < 0)
					goto fail;
			}
			
			//[3]:keyword没有在变量名对象表中出现
			if(j >= co->co_argcount){
				PyDict_SetItem(kwdict, keyword, value);
			}
			//keyword在变量名对象表中出现
			else{
				SETLOCAL(j, value);
			}
			
		}
	}
}

bubuko.com,布布扣

6.函数中局部变量的访问
局部变量也是利用LOAD_FAST和 STORE_FAST来操作的,也是存放在f_localsplus中运行时栈前面的那段内存空间中。
函数的实现中没有使用local名字空间,是因为函数中的局部变量总是固定不变的,编译时就能确定,不用动态查找。
??那local名字空间还有什么用?


7.嵌套函数、闭包和decorator
名字空间-->动态-->可将其静态化-->闭包
闭包:名字空间与函数绑定后的结果,通常利用嵌套函数来完成。


co_cellvars: tuple,保存嵌套作用域中使用的变量名
co_freevars: tuple,保存外部作用域中的变量名
f_localsplus指向的内存有四部分:运行时栈、局部变量、cell对象、free对象
bubuko.com,布布扣

外部函数的co_cellvars记录着嵌套作用域中使用的变量名,在创建内部函数的时候,将
co_cellvars中的符号对应的值保存到内存函数的f_localsplus的cell对象中,从而实现闭包。
在访问的时候


def get_func():
LOAD_CONST 0
MAKE_FUNCTION
STORE_NAME 0
	value = "inner"
	LOAD_CONST 1
	STORE_DEREF 0
	def inner_func():
	LOAD_CLOSUER 0
	BUILD_TUPLE
	LOAD_CONST 2
	MAKE_CLOSURE 0
	STORE_FAST 0
		print value
		LOAD_DEREF 0
		PRINT_ITEM
		PRINT_NEWLINE
		LOAD_CONST 0
		RETURN_VALUE
	return inner_func
	LOAD_FAST 0
	RETURN_VALUE
show_value = get_func()
LOAD_NAME 0
CALL_FUNCTION 0
STORE_NAME 1
show_value()
LOAD_NAME 1
CALL_FUNCTION 0
POP_TOP
LOAD_CONST
RETURN_VALUE

在PyEval_EvalCodeEx中,Python虚拟机会如同处理默认参数一样,将co_cellvars中的东西拷贝到新建的PyFrameObject的
f_localsplus中


定义时
bubuko.com,布布扣
bubuko.com,布布扣

调用时
bubuko.com,布布扣


Decorator --> 好像是回调函数
在python中,decorator是func = should_say(func)的一种包装方式,理解decorator的关键在于理解closure
def should_say(fn):
	def say(*args):
		print 'say something ...'
		fn(*args)
	return say
@should_say
def func():
	print 'in func'

和下面的其实是一样的。
#...
def func():
	print 'in func'
func = should_say(func)
func()


《python源码剖析》笔记 python虚拟机中的函数机制,布布扣,bubuko.com

《python源码剖析》笔记 python虚拟机中的函数机制

标签:style   class   blog   code   http   tar   

原文地址:http://blog.csdn.net/zhengsenlie/article/details/33326399

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!