扩展位置参数和扩展键参数

Python虚拟机函数机制之参数类别(三)的例3和例4中,我们看到了使用扩展位置参数和扩展键参数时指示参数个数的变量的值。在那里,我们发现在函数内部没有使用局部变量时,co_nlocals和co_argcount的值已经不再相同了。从它们的差异我们猜测,当使用扩展位置参数*args或者扩展键参数**kwargs时,实际是作为一个局部变量来实现的。同时,我们还猜测,在Python内部,*args是由PyTuppleObject实现的,而**kwargs是由PyDictObject对象实现的。本章中,将深入地剖析Python是如何实现扩展位置参数和扩展键参数

  1. def Py_Func(value, *args, **kwargs):
  2. pass

  

  1. Py_Func(-1, 1, 2, a=3, b=4)
  2. //字节码指令
  3. 9 LOAD_NAME 0 (Py_Func)
  4. 12 LOAD_CONST 1 (-1)
  5. 15 LOAD_CONST 2 (1)
  6. 18 LOAD_CONST 3 (2)
  7. 21 LOAD_CONST 4 ('a')
  8. 24 LOAD_CONST 5 (3)
  9. 27 LOAD_CONST 6 ('b')
  10. 30 LOAD_CONST 7 (4)
  11. 33 CALL_FUNCTION 515
  12.  
  13. >>> Py_Func(-1, 1, 2, a=3, b=4)
  14. [call_function]:na=3, nk=2, n=7
  15. [call_function]:co->co_argcount=1, co->co_nlocals=3

  

Python虚拟机的执行路径最终将进入PyEval_EvalCodeEx,这个函数我们之前已经捋过几遍,下面,我们来看对扩展位置参数的处理

ceval.c

  1. PyObject *
  2. PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
  3. PyObject **args, int argcount, //位置参数的信息
  4. PyObject **kws, int kwcount,//键参数的信息
  5. PyObject **defs, int defcount, //函数默认参数的信息
  6. PyObject *closure)
  7. {
  8. register PyFrameObject *f;
  9. register PyObject *retval = NULL;
  10. register PyObject **fastlocals, **freevars;
  11. PyThreadState *tstate = PyThreadState_GET();
  12. PyObject *x, *u;
  13.  
  14. //创建PyFrameObject对象
  15. f = PyFrame_New(tstate, co, globals, locals);
  16. if (f == NULL)
  17. return NULL;
  18.  
  19. fastlocals = f->f_localsplus;
  20. freevars = f->f_localsplus + co->co_nlocals;
  21. //[1]:判断是否需要处理扩展位置参数或扩展键参数
  22. if (co->co_argcount > 0 ||
  23. co->co_flags & (CO_VARARGS | CO_VARKEYWORDS))
  24. {
  25. int i;
  26. int n = argcount;
  27. PyObject *kwdict = NULL;
  28. if (argcount > co->co_argcount)
  29. {
  30. if (!(co->co_flags & CO_VARARGS))
  31. {
  32. PyErr_Format(PyExc_TypeError,
  33. "%.200s() takes %s %d "
  34. "%sargument%s (%d given)",
  35. PyString_AsString(co->co_name),
  36. defcount ? "at most" : "exactly",
  37. co->co_argcount,
  38. kwcount ? "non-keyword " : "",
  39. co->co_argcount == 1 ? "" : "s",
  40. argcount);
  41. goto fail;
  42. }
  43. n = co->co_argcount;
  44. }
  45. //[2]:设置位置参数的参数值
  46. for (i = 0; i < n; i++)
  47. {
  48. x = args[i];
  49. Py_INCREF(x);
  50. SETLOCAL(i, x);
  51. }
  52. //[3]:处理扩展位置参数
  53. if (co->co_flags & CO_VARARGS)
  54. {
  55. //[4]:将PyTuppleObject对象放入到f_localsplus中
  56. u = PyTuple_New(argcount - n);
  57. if (u == NULL)
  58. goto fail;
  59. SETLOCAL(co->co_argcount, u);
  60. //[5]:将扩展位置参数放入到PyTuppleObject中
  61. for (i = n; i < argcount; i++)
  62. {
  63. x = args[i];
  64. Py_INCREF(x);
  65. PyTuple_SET_ITEM(u, i - n, x);
  66. }
  67. }
  68. ……
  69. }
  70. ……
  71. }

  

在Python编译一个函数时,如果在其形式参数中发现*args这样的扩展位置参数的参数形式,那么Python会在所编译得到的PyCodeObject对象的co_flags中添加一个标识符号:CO_VARARGS,表示该函数在被调用时需要处理扩展位置参数。同样,对于函数中包含**kwargs这样的参数,Python将在co_flags中添加CO_VARKEYWORDS标识。所以,对于含有扩展位置参数和扩展键参数的函数,上述代码[1]处都将成立

前面我们已经知道,在PyEval_EvalCodeEx中,argcount其实就是na的值,一旦argcount > co->co_argcount成立,就意味着函数调用时传递了扩展位置参数。在代码[2]处设置了正规的位置参数后,就会进入代码[3]处对扩展位置参数的处理过程。Python虚拟机会在代码[4]处创建一个长度为(argcount - n)的PyTuppleObject对象,将其放入f_localsplus,这里放置的索引位置是co->co_argcount,然后在代码[5]处将所有的扩展位置参数一股脑地塞入这个PyTuppleObject对象。注意,这里是先创建一个空的PyTuppleObject对象,将其放到f_localsplus,然后才根据传入的参数填充PyTuppleObject对象

了解扩展位置参数的传递机制之后,我们再来看看扩展键参数的传递机制

ceval.c

  1. PyObject *
  2. PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
  3. PyObject **args, int argcount, //位置参数的信息
  4. PyObject **kws, int kwcount,//键参数的信息
  5. PyObject **defs, int defcount, //函数默认参数的信息
  6. PyObject *closure)
  7. {
  8. ……
  9. if (co->co_argcount > 0 ||
  10. co->co_flags & (CO_VARARGS | CO_VARKEYWORDS))
  11. {
  12. int i;
  13. int n = argcount;
  14. PyObject *kwdict = NULL;
  15. //[1]:创建一个PyDictObject对象,并将其放到f_localsplus中
  16. if (co->co_flags & CO_VARKEYWORDS)
  17. {
  18. kwdict = PyDict_New();
  19. if (kwdict == NULL)
  20. goto fail;
  21. i = co->co_argcount;
  22. //[2]:PyDictObject对象必须在PyTuppleObject对象之后
  23. if (co->co_flags & CO_VARARGS)
  24. i++;
  25. SETLOCAL(i, kwdict);
  26. }
  27. //遍历键参数,确定函数的def语句中是否出现了键参数的名字
  28. for (i = 0; i < kwcount; i++)
  29. {
  30. PyObject *keyword = kws[2 * i];
  31. PyObject *value = kws[2 * i + 1];
  32. int j;
  33.  
  34. //在函数的变量名对象表中寻找keyword
  35. for (j = 0; j < co->co_argcount; j++)
  36. {
  37. PyObject *nm = PyTuple_GET_ITEM(
  38. co->co_varnames, j);
  39. int cmp = PyObject_RichCompareBool(
  40. keyword, nm, Py_EQ);
  41. if (cmp > 0)
  42. break;
  43. else if (cmp < 0)
  44. goto fail;
  45. }
  46. //[3]:keyword没有在变量名对象表中出现
  47. if (j >= co->co_argcount)
  48. {
  49. if (kwdict == NULL)
  50. {
  51. PyErr_Format(PyExc_TypeError,
  52. "%.200s() got an unexpected "
  53. "keyword argument '%.400s'",
  54. PyString_AsString(co->co_name),
  55. PyString_AsString(keyword));
  56. goto fail;
  57. }
  58. PyDict_SetItem(kwdict, keyword, value);
  59. }
  60. else
  61. { //keyword在变量名对象表中出现
  62. if (GETLOCAL(j) != NULL)
  63. {
  64. PyErr_Format(PyExc_TypeError,
  65. "%.200s() got multiple "
  66. "values for keyword "
  67. "argument '%.400s'",
  68. PyString_AsString(co->co_name),
  69. PyString_AsString(keyword));
  70. goto fail;
  71. }
  72. Py_INCREF(value);
  73. SETLOCAL(j, value);
  74. }
  75. }
  76.  
  77. }
  78. ……
  79. }

  

其实,扩展键参数的传递机制与键参数的传递机制有很大的关系,之前分析函数参数的默认值机制时我们已经看过键参数的传递机制了,在那里我们知道Python会在函数的PyCodeObject对象的变量名对象表(co_varnames)中查找键参数的名字,只有在查找失败时,才能确定该键参数应该属于一个扩展键参数

和扩展位置参数的实现一样,在代码的[1]处,Python虚拟机创建了一个PyDictObject对象,并且将该对象放入到PyFrameObject对象的f_localsplus中。值的注意的是,在代码[2]处,这个判断及其后的操作保证:当函数拥有扩展位置参数时,扩展键参数的PyDictObject对象在f_localsplus中的位置一定在扩展位置参数PyTuppleObject对象之后的下一个位置

然后,对调用函数传递进来的每一个键参数,Python虚拟机都会判断它是一般的键参数,还是扩展键参数,如果是扩展键参数,就在代码[3]处将其插入到PyDictObject对象中

Python虚拟机函数机制之扩展位置参数和扩展键参数(六)的更多相关文章

  1. Python虚拟机函数机制之位置参数的默认值(五)

    位置参数的默认值 在Python中,允许函数的参数有默认值.假如函数f的参数value的默认值是1,在我们调用函数时,如果传递了value参数,那么f调用时value的值即为我们传递的值,如果调用时没 ...

  2. Python虚拟机函数机制之参数类别(三)

    参数类别 我们在Python虚拟机函数机制之无参调用(一)和Python虚拟机函数机制之名字空间(二)这两个章节中,分别PyFunctionObject对象和函数执行时的名字空间.本章,我们来剖析一下 ...

  3. Python虚拟机函数机制之名字空间(二)

    函数执行时的名字空间 在Python虚拟机函数机制之无参调用(一)这一章中,我们对Python中的函数调用机制有个大概的了解,在此基础上,我们再来看一些细节上的问题.在执行MAKE_FUNCTION指 ...

  4. Python虚拟机函数机制之位置参数(四)

    位置参数的传递 前面我们已经分析了无参函数的调用过程,我们来看看Python是如何来实现带参函数的调用的.其实,基本的调用流程与无参函数一样,而不同的是,在调用带参函数时,Python虚拟机必须传递参 ...

  5. Python虚拟机函数机制之闭包和装饰器(七)

    函数中局部变量的访问 在完成了对函数参数的剖析后,我们再来看看,在Python中,函数的局部变量时如何实现的.前面提到过,函数参数也是一种局部变量.所以,其实局部变量的实现机制与函数参数的实现机制是完 ...

  6. Python虚拟机函数机制之无参调用(一)

    PyFunctionObject对象 在Python中,任何一个东西都是对象,函数也不例外.函数这种抽象机制,是通过一个Python对象——PyFunctionObject来实现的 typedef s ...

  7. Python虚拟机类机制之绑定方法和非绑定方法(七)

    Bound Method和Unbound Method 在Python中,当对作为属性的函数进行引用时,会有两种形式,一种称为Bound Method,这种形式是通过类的实例对象进行属性引用,而另一种 ...

  8. Python虚拟机类机制之instance对象(六)

    instance对象中的__dict__ 在Python虚拟机类机制之从class对象到instance对象(五)这一章中最后的属性访问算法中,我们看到“a.__dict__”这样的形式. # 首先寻 ...

  9. Python虚拟机类机制之从class对象到instance对象(五)

    从class对象到instance对象 现在,我们来看看如何通过class对象,创建instance对象 demo1.py class A(object): name = "Python&q ...

随机推荐

  1. 使用CRA开发的基于React的UI组件发布到内网NPM上去

    前言:构建的ES组件使用CNPM发布内网上过程 1. 使用Create-React-APP开的组件 如果直接上传到NPM,你引用的时候会报: You may need an appropriate l ...

  2. CSS中垂直水平居中

    方法一:使用flex布局,父级元素设置justify-content和align-items <div class="cont"> <div class=&quo ...

  3. 只用jsp实现同样的Servlet功能

    Jsp最终都会转化成java形式的Servlet执行,因此也可以说Jsp的本质就是Servlet,在jsp执行后,会在服务器上(例如tomcat中)生成.java以及.class文件.具体执行过程如下 ...

  4. 前端js优化方案(二)持续更新

    由于上篇篇幅过长,导致编辑出了问题,另开一篇文章继续: (4)减少迭代次数,最广为人知的一种限制循环迭代次数的模式被称为“达夫设备(Duff`s Device)” Duff`s Device的理念是: ...

  5. 2017微软骇客马拉松精彩大回Fun:不一样的Hacker,一Young的Cool

    丹棱君有话说:一年一度激动人心的骇客马拉松大会结束了!这场内部创意大比拼硕果累累,丹棱君准备好了 6 组 Cool 骇客的别 Young 作品——沉浸式销售工具如何能守得“云”开见月明?“骇客马拉松超 ...

  6. 实现strcpy函数

    不使用库函数,实现strcpy函数: char *my_strcpy(char *t,char *s){ char *strDest=t; if(t==NULL && s==NULL) ...

  7. SharePoint 2016 如何修改Library 地址

    Scenario #1 如何为一个Library 修改下访问 网络路径地址 1.点击library,点开open with explorer,使用Windows资源管理器打开文档库 2.在文件夹层次结 ...

  8. Java-Web总结03

    *1 dom4j解析器   1)CRUD的含义:CreateReadUpdateDelete增删查改   2)XML解析器有二类,分别是DOM和SAX. a)DOM一次性将整个XML文件读到内存,形成 ...

  9. Failed to load property source from location 'classpath:/applica)

    : 1.注释错误(application.yml用的是#注释) 2.缩进采用tab而不是空格引起的(不同配置之间也不能有tab出现,否则会报错) 3.冒号后面必须有空格否则会报错

  10. PAT (Basic Level) Practise (中文)- 1007. 素数对猜想 (20)

    http://www.patest.cn/contests/pat-b-practise/1007 让我们定义 dn 为:dn = pn+1 - pn,其中 pi 是第i个素数.显然有 d1=1 且对 ...