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

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

def Py_Func(value, *args, **kwargs):
pass

  

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

  

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

ceval.c

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; //创建PyFrameObject对象
f = PyFrame_New(tstate, co, globals, locals);
if (f == NULL)
return NULL; fastlocals = f->f_localsplus;
freevars = f->f_localsplus + co->co_nlocals;
//[1]:判断是否需要处理扩展位置参数或扩展键参数
if (co->co_argcount > 0 ||
co->co_flags & (CO_VARARGS | CO_VARKEYWORDS))
{
int i;
int n = argcount;
PyObject *kwdict = NULL;
if (argcount > co->co_argcount)
{
if (!(co->co_flags & CO_VARARGS))
{
PyErr_Format(PyExc_TypeError,
"%.200s() takes %s %d "
"%sargument%s (%d given)",
PyString_AsString(co->co_name),
defcount ? "at most" : "exactly",
co->co_argcount,
kwcount ? "non-keyword " : "",
co->co_argcount == 1 ? "" : "s",
argcount);
goto fail;
}
n = co->co_argcount;
}
//[2]:设置位置参数的参数值
for (i = 0; i < n; i++)
{
x = args[i];
Py_INCREF(x);
SETLOCAL(i, x);
}
//[3]:处理扩展位置参数
if (co->co_flags & CO_VARARGS)
{
//[4]:将PyTuppleObject对象放入到f_localsplus中
u = PyTuple_New(argcount - n);
if (u == NULL)
goto fail;
SETLOCAL(co->co_argcount, u);
//[5]:将扩展位置参数放入到PyTuppleObject中
for (i = n; i < argcount; i++)
{
x = args[i];
Py_INCREF(x);
PyTuple_SET_ITEM(u, i - n, x);
}
}
……
}
……
}

  

在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

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_argcount > 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();
if (kwdict == NULL)
goto fail;
i = co->co_argcount;
//[2]:PyDictObject对象必须在PyTuppleObject对象之后
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)
break;
else if (cmp < 0)
goto fail;
}
//[3]:keyword没有在变量名对象表中出现
if (j >= co->co_argcount)
{
if (kwdict == NULL)
{
PyErr_Format(PyExc_TypeError,
"%.200s() got an unexpected "
"keyword argument '%.400s'",
PyString_AsString(co->co_name),
PyString_AsString(keyword));
goto fail;
}
PyDict_SetItem(kwdict, keyword, value);
}
else
{ //keyword在变量名对象表中出现
if (GETLOCAL(j) != NULL)
{
PyErr_Format(PyExc_TypeError,
"%.200s() got multiple "
"values for keyword "
"argument '%.400s'",
PyString_AsString(co->co_name),
PyString_AsString(keyword));
goto fail;
}
Py_INCREF(value);
SETLOCAL(j, value);
}
} }
……
}

  

其实,扩展键参数的传递机制与键参数的传递机制有很大的关系,之前分析函数参数的默认值机制时我们已经看过键参数的传递机制了,在那里我们知道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. c#基础 path 类的各种套路

    string str = @"C:\3000soft\Red Spider\Data\Message\老赵.wav"; //获得文件名 Console.WriteLine(Path ...

  2. 关于 SQL Server Reporting Services 匿名登录的解决方案

    每次访问报表都需要windows验证,这样的报表给客户确实很说不过去. SSRS 可以匿名登录的设定步骤: 环境: 开发工具:SQL Server Business Intelligence Deve ...

  3. 编译安装php容易出现的问题以及解决办法

    http://crybit.com/20-common-php-compilation-errors-and-fix-unix/

  4. 网页转PDF作为邮件附件

    Nuget 引入 OpenHtmlToPdf using (WebClient wc = new WebClient()) { wc.Encoding = Encoding.UTF8; wc.UseD ...

  5. 【css】css2实现两列三列布局的方法

    前言 对于 flex 弹性布局相信大家都有所了解,它是 css3 中的属性,然而它具有一定的兼容性问题.楼主前几天面试时遇到了面试官需要设计一个两列布局,我当然就说父元素 flex 吧哩吧啦,然而需要 ...

  6. JavaWeb_05_xml相关&dtd快速入门

    学东西怎么学,是什么,能做什么,怎么去做!! 1.xml的简介 1.eXtensible Markup Language:可扩展标记型语言 标记型语言:html是标记型语言 也是使用标签来操作 可扩展 ...

  7. ajax提交表单无法验证easyui的验证选项(比如required等)

    在实际开发中,遇到ajax方式提交表单没法验证easyui的验证选项,这对实际用户体验造成了很大的困扰.当然,这也是理所当然的事情.   解决办法:使用jquery中ajax的beforeSend事件 ...

  8. JNI接口的使用(简单版)

    详见 http://b6ec263c.wiz03.com/share/s/2SX2oY0nX4f32CY5ax1bapaL2Qtc5q0tIQjG2yfwaU1MX4Ye

  9. 干净卸载 Cloudera CDH 5 beta2

    Cloudera 的官方介绍: http://www.cloudera.com/content/cloudera-content/cloudera-docs/CM4Ent/4.8.1/Cloudera ...

  10. Spark集锦

    1 Spark官网  http://spark.apache.org/ 2 Spark书籍 http://down.51cto.com/tag-spark%E4%B9%A6%E7%B1%8D.html