《python源代码剖析》笔记 python虚拟机中的函数机制
本文为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()创建了函数对象
PYCodeObject:一个Code Block的静态信息。比方a = 1,符号a和值1以及它们的联系是一种静态信息,分别存储在
PyCodeObject的常量表co_consts,符号表co_names以及字节码序列co_code中
PyFunctionObject:包括函数的静态信息,由func_code指向函数代码相应的PyCodeObject对象,还包括了函数在运行时
的动态信息,如func_globals。
名字空间里键值对是动态信息,必须在执行时动态创建
一段代码仅仅能相应一个PyCodeObject,却能够相应多个PyFunctionObject。
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所看到的
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名字空间实际上是同一个名字空间
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)
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 agecall_function --> fast_function
位置參数的訪问
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中存储的符号相应的值对象。
位置參数的默认值
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
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); } } } }
6.函数中局部变量的訪问
局部变量也是利用LOAD_FAST和 STORE_FAST来操作的,也是存放在f_localsplus中执行时栈前面的那段内存空间中。
函数的实现中没有使用local名字空间,是由于函数中的局部变量总是固定不变的,编译时就能确定,不用动态查找。
??那local名字空间还有什么用?
7.嵌套函数、闭包和decorator
名字空间-->动态-->可将其静态化-->闭包
闭包:名字空间与函数绑定后的结果,通常利用嵌套函数来完毕。
co_cellvars: tuple,保存嵌套作用域中使用的变量名
co_freevars: tuple,保存外部作用域中的变量名
f_localsplus指向的内存有四部分:执行时栈、局部变量、cell对象、free对象
外部函数的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中
定义时
调用时
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()