当前位置:   article > 正文

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

_pyeval_eval

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


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


2.PyFuctionObject对象

  1. typedef struct {
  2. PyObject_HEAD
  3. PyObject *func_code; //对应函数编译后的PyCodeObject对象
  4. PyObject *func_globals; //函数运行时的global空间
  5. PyObject *func_defaults; //默认参数(tuple或NULL)
  6. PyObject *func_closure; //NULL or a tuple of cell objects,用于实现closure
  7. PyObject *func_doc; //函数的文档(PyStringObject)
  8. PyObject *func_name; //函数名称,函数的__name__属性,(PyStringObject)
  9. PyObject *func_dict; //函数的__dict__属性(PyDictObject或NULL)
  10. PyObject *func_weakreflist;
  11. PyObject *func_module; //函数的__module__,可以是任何对象
  12. } 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
  1. def f():
  2. #LOAD_CONST 0
  3. #MAKE_FUNCTION 0
  4. #STORE_NAME 0
  5. print "Function"
  6. #LOAD_CONST 1
  7. #PRINT_ITEM
  8. #PRINT_NEWLINE
  9. #LOAD_CONST
  10. #RETURN_VALUE
  11. f()
  12. #LOAD_NAME 0
  13. #CALL_FUNCTION 0
  14. #POP_TOP
  15. #LOAD_CONST 1
  16. #RETURN_VALUE


 

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

 call_function函数调用

  1. static PyObject *
  2. call_function(PyObject ***pp_stack, int oparg
  3. #ifdef WITH_TSC
  4. , uint64* pintr0, uint64* pintr1
  5. #endif
  6. )
  7. {
  8. //[1]:处理函数参数信息
  9. int na = oparg & 0xff; //位置参数、扩展位置参数个数
  10. int nk = (oparg>>8) & 0xff; //键参数、扩展键参数个数
  11. int n = na + 2 * nk; //总共的参数个数 nk*2是因为(键,值)对中键和值各占一个位置
  12. //[2]:获得PyFunctionObject对象
  13. PyObject **pfunc = (*pp_stack) - n - 1;
  14. PyObject *func = *pfunc;
  15. PyObject *x, *w;
  16. if (PyCFunction_Check(func) && nk == 0) {
  17. //...
  18. } else {
  19. if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
  20. } else
  21. //[3]:对PyFunctionObject对象进行调用
  22. if (PyFunction_Check(func))
  23. x = fast_function(func, pp_stack, n, na, nk);
  24. else
  25. x = do_call(func, pp_stack, na, nk);
  26. //...
  27. }
  28. //...
  29. return x;
  30. }

fast_function
对于一般函数(除Python独有的函数(如Draw(x, *key, **dict))外的函数),PyEval_EvalFrameEx,进入一个新的PyFrameObject(栈桢)环境中开始执行新的字节码指令序列的循环
另一条路径,PyEval_EvalCodeEx
  1. static PyObject *
  2. fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
  3. {
  4. PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
  5. PyObject *globals = PyFunction_GET_GLOBALS(func);
  6. PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
  7. PyObject **d = NULL;
  8. int nd = 0;
  9. //...
  10. //[1]:一般函数的快速通道
  11. if (argdefs == NULL && co->co_argcount == n && nk==0 &&
  12. co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
  13. PyFrameObject *f;
  14. PyObject *retval = NULL;
  15. PyThreadState *tstate = PyThreadState_GET();
  16. PyObject **fastlocals, **stack;
  17. int i;
  18. f = PyFrame_New(tstate, co, globals, NULL);
  19. //...
  20. retval = PyEval_EvalFrameEx(f,0);
  21. //...
  22. return retval;
  23. }
  24. if (argdefs != NULL) {
  25. d = &PyTuple_GET_ITEM(argdefs, 0);
  26. nd = Py_SIZE(argdefs);
  27. }
  28. return PyEval_EvalCodeEx(co, globals,
  29. (PyObject *)NULL, (*pp_stack)-n, na,
  30. (*pp_stack)-2*nk, nk, d, nd,
  31. PyFunction_GET_CLOSURE(func));
  32. }

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.
  1. def fun(a, b):
  2. pass
  3. fun(1, b = 2)





5.位置参数的传递
  1. def f(name, age):
  2. #LOAD_CONST 0
  3. #MAKE_FUNCTION 0
  4. #STORE_NAME 0
  5. age += 5
  6. #LOAD_FAST 1
  7. #LOAD_CONST 5
  8. #INPLACE_ADD
  9. #STORE_FAST 1
  10. print "[", name, ",", age, "]"
  11. #LOAD_CONST 2
  12. #PRINT_ITEM
  13. #LOAD_FAST 0
  14. #PRINT_ITEM
  15. #LOAD_CONST 3
  16. #PRINT_ITEM
  17. #LOAD_FAST 1
  18. #PRINT_ITEM
  19. #LOAD_CONST 4
  20. #PRINT_ITEM
  21. #PRINT_NEWLINE
  22. #LOAD_CONST 0
  23. #RETURN_VALUE
  24. age = 5;
  25. print age
  26. f("Robert", age)
  27. #LOAD_NAME 0
  28. #LOAD_CONST 2
  29. #LOAD_NAME 1
  30. #CALL_FUNCTION 2
  31. #POP_TOP
  32. print age
call_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中存储的符号对应的值对象。


位置参数的默认值
  1. def f(a = 1, b = 2):
  2. #LOAD_CONST 0
  3. #LOAD_CONST 1
  4. #LOAD_CONST 2
  5. #MAKE_FUNCTION 2
  6. #STORE_NAME 0
  7. print a + b
  8. #LOAD_FAST 0
  9. #LOAD_FAST 1
  10. #BINARY_ADD
  11. #PRINT_ITEM
  12. #PRINT_NEWLINE
  13. #LOAD_CONST 0
  14. #RETURN_VALUE
  15. f()
  16. #LOAD_NAME 0
  17. #CALL_FUNCTION 0
  18. #POP_TOP
  19. f(b=3)
  20. #LOAD_NAME 0
  21. #LOAD_CONST 3
  22. #LOAD_CONST 4
  23. #CALL_FUNCTION 256
  24. #POP_TOP
  25. #LOAD_CONST 5
  26. #RETURN_VALUE

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

  1. PyObject *
  2. PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
  3. PyObject **args, int argcount, PyObject **kws, int kwcount,
  4. PyObject **defs, int defcount, PyObject *closure)
  5. {
  6. register PyFrameObject *f;
  7. register PyObject *retval = NULL;
  8. register PyObject **fastlocals, **freevars;
  9. PyThreadState *tstate = PyThreadState_GET();
  10. PyObject *x, *u;
  11. //[1]:创建PyFrameObject对象
  12. f = PyFrame_New(tstate, co, globals, locals);
  13. fastlocals = f->f_localsplus;
  14. freevars = f->f_localsplus + co->co_nlocals;
  15. //[a]:遍历键参数,确定函数的def语句中是否出现了键参数的名字
  16. for (i = 0; i < kwcount; i++) {
  17. PyObject *keyword = kws[2*i];
  18. PyObject *value = kws[2*i + 1];
  19. int j;
  20. //[b]:在函数的变量名表中寻找keyword
  21. //...
  22. for (j = 0; j < co->co_argcount; j++) {
  23. PyObject *nm = co_varnames[j];
  24. int cmp = PyObject_RichCompareBool(
  25. keyword, nm, Py_EQ);
  26. if (cmp > 0)
  27. goto kw_found;
  28. else if (cmp < 0)
  29. goto fail;
  30. }
  31. if (kwdict == NULL) {
  32. PyObject *kwd_str = kwd_as_string(keyword);
  33. if (kwd_str) {
  34. PyErr_Format(PyExc_TypeError,
  35. "%.200s() got an unexpected "
  36. "keyword argument '%.400s'",
  37. PyString_AsString(co->co_name),
  38. PyString_AsString(kwd_str));
  39. Py_DECREF(kwd_str);
  40. }
  41. goto fail;
  42. }
  43. PyDict_SetItem(kwdict, keyword, value);
  44. continue;
  45. kw_found:
  46. if (GETLOCAL(j) != NULL) {
  47. goto fail;
  48. }
  49. Py_INCREF(value);
  50. SETLOCAL(j, value);
  51. }
  52. if (co->co_argcount > 0 ||
  53. co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)) {
  54. int i;
  55. int n = argcount;
  56. PyObject *kwdict = NULL;
  57. //n为CALL_FUNCTION的参数指示的传入的位置参数个数,即na,这里为0
  58. //...
  59. //[2]:判断是否使用参数的默认值
  60. if (argcount < co->co_argcount) {
  61. //m 一般位置参数的个数
  62. int m = co->co_argcount - defcount;
  63. //[3]:函数调用者必须传递一般位置参数的参数值
  64. for (i = argcount; i < m; i++) {
  65. if (GETLOCAL(i) == NULL) {
  66. goto fail;
  67. }
  68. }
  69. //[4]:n > m意味着调用者希望替换一些默认位置参数的默认值
  70. if (n > m)
  71. i = n - m;
  72. else
  73. i = 0;
  74. //[5]:设置默认位置参数的默认值
  75. for (; i < defcount; i++) {
  76. if (GETLOCAL(m+i) == NULL) {
  77. PyObject *def = defs[i];
  78. Py_INCREF(def);
  79. SETLOCAL(m+i, def);
  80. }
  81. }
  82. }
  83. }
  84. retval = PyEval_EvalFrameEx(f,0);
  85. return retval;
  86. }


当最终需要设置默认值的参数个数确定之后,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



  1. PyObject *
  2. PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
  3. PyObject **args, int argcount, PyObject **kws, int kwcount,
  4. PyObject **defs, int defcount, PyObject *closure)
  5. {
  6. register PyFrameObject *f;
  7. register PyObject **fastlocals, **freevars;
  8. PyThreadState *tstate = PyThreadState_GET();
  9. PyObject *x, *u;
  10. //创建PyFrameObject对象
  11. f = PyFrame_New(tstate, co, globals, locals);
  12. fastlocals = f->localsplus;
  13. freevars = f->f_localsplus + f->f_nlocals;
  14. //[1]:判断是否需要处理扩展位置参数或扩展键参数
  15. if(co->co_argcount > 0 || co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)){
  16. int i;
  17. int n = argcount;
  18. if(argcount > co->co_argcount ){
  19. n = co->co_argcount;
  20. }
  21. //[2]:设置位置参数的参数值
  22. for(i = 0; i < n; i++){
  23. x = args[i];
  24. SETLOCAL(i, x);
  25. }
  26. //[3]:处理扩展位置参数
  27. if(co->co_flags & CO_VARARGS){
  28. //[4]:将PyTupleObject对象放入到f_localsplus中
  29. u = PyTuple_New(argcount - n );
  30. SETLOCAL(co->co_argcount, u);
  31. //[5]:将扩展位置参数放入到PyTupleObject中
  32. for( i = n; i < argcount; i++){
  33. x = args[i];
  34. PyTuple_SET_ITEM(u, i - n, x);
  35. }
  36. }
  37. }
  38. }


Python虚拟机会在函数的PyCodeObject对象的变量名对象表(co_varnames)中查找键参数的名字,只有在查找失败时,才能
确定该键参数应该属于一个扩展键参数。
  1. PyObject *
  2. PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
  3. PyObject **args, int argcount, PyObject **kws, int kwcount,
  4. PyObject **defs, int defcount, PyObject *closure)
  5. {
  6. //...
  7. if(co->co_flags > 0 || co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)){
  8. int i;
  9. int n = argcount;
  10. PyObject *kwdict = NULL;
  11. //...
  12. //[1]:创建PyDictObject对象,并将其放到f_localsplus中
  13. if(co->co_flags & CO_VARKEYWORDS){
  14. kwdict = PyDict_New();
  15. i = co->co_argcount;
  16. //[2]:PyDictObject对象必须在PyTupleObject之后
  17. if(co->co_flags & CO_VARARGS)
  18. i++;
  19. SETLOCAL(i, kwdict);
  20. }
  21. //遍历键参数,确定函数的def语句中是否出现了键参数的名字
  22. for(i = 0; i < kwcount; i++){
  23. PyObject *keyword = kws[2 * i];
  24. PyObject *value = kws[2 * i + 1];
  25. int j;
  26. //在函数的变量名表中寻找keyword
  27. for(j = 0; j < co->co_argcount; j++){
  28. PyObject *nm = PyTuple_GET_ITEM(co->co_varnames, j);
  29. int cmp = PyObject_RichCompareBool(keyword, nm, Py_EQ);
  30. if(cmp > 0)//在co_varnames中找到keyword
  31. break;
  32. else if (cmp < 0)
  33. goto fail;
  34. }
  35. //[3]:keyword没有在变量名对象表中出现
  36. if(j >= co->co_argcount){
  37. PyDict_SetItem(kwdict, keyword, value);
  38. }
  39. //keyword在变量名对象表中出现
  40. else{
  41. SETLOCAL(j, value);
  42. }
  43. }
  44. }
  45. }



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对象中,从而实现闭包。
在访问的时候


  1. def get_func():
  2. LOAD_CONST 0
  3. MAKE_FUNCTION
  4. STORE_NAME 0
  5. value = "inner"
  6. LOAD_CONST 1
  7. STORE_DEREF 0
  8. def inner_func():
  9. LOAD_CLOSUER 0
  10. BUILD_TUPLE
  11. LOAD_CONST 2
  12. MAKE_CLOSURE 0
  13. STORE_FAST 0
  14. print value
  15. LOAD_DEREF 0
  16. PRINT_ITEM
  17. PRINT_NEWLINE
  18. LOAD_CONST 0
  19. RETURN_VALUE
  20. return inner_func
  21. LOAD_FAST 0
  22. RETURN_VALUE
  23. show_value = get_func()
  24. LOAD_NAME 0
  25. CALL_FUNCTION 0
  26. STORE_NAME 1
  27. show_value()
  28. LOAD_NAME 1
  29. CALL_FUNCTION 0
  30. POP_TOP
  31. LOAD_CONST
  32. RETURN_VALUE

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


定义时


调用时



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

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


声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号