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

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. Java常用函数式接口--Consumer接口andThen()方法使用案例(二)

    Java常用函数式接口--Consumer接口使用案例

  2. AngularJS(四):控制器、事件

    本文也同步发表在我的公众号“我的天空” 控制器 控制器可以说是AngularJS中最重要的部分了!之前的一些示例,除了第一讲的示例以外,我们对于AngularJS的使用都集中在HTML部分,其实Ang ...

  3. Google Chrome 浏览器的备用(离线)安装程序

    Google Chrome 浏览器的备用(离线)安装程序(适用于 Windows) 如果您在使用 http://www.google.com/chrome 上的标准安装程序下载 Chrome 浏览器时 ...

  4. 常用浏览器User-Agent大全

    =======================PC浏览器======================== OperaMozilla/5.0 (Windows NT 6.1; WOW64) AppleW ...

  5. Ecshop:ecshop nginx下实现url静态化

    1.在nginx/conf/tuwen.com.conf中添加: include ecshop.conf; 2.编辑nginx/ecshop.conf: location / { rewrite &q ...

  6. Android商城开发系列(二)——App启动欢迎页面制作

    商城APP一般都会在应用启动时有一个欢迎界面,下面我们来实现一个最简单的欢迎页开发:就是打开商城App,先出现欢迎界面,停留几秒钟,自动进入应用程序的主界面. 首先先定义WelcomeActivity ...

  7. acdream 小晴天老师系列——我有一个数列! (ST算法)

    小晴天老师系列——我有一个数列! Time Limit: 20000/10000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Others)S ...

  8. HDU 1850 Being a Good Boy in Spring Festival 在春节做乖孩子(Nim博弈,微变形)

    题意: 思路: 如果全部扑克牌数目异或的结果ans为0,则必输,输出0.否则,必须要给对方一个P状态,可以对所有扑克堆进行逐个排查,将ans^a[i]就可以得到除了a[i]之外其他扑克数的异或结果tm ...

  9. Java 集合框架_下

    Map接口 特点: [1]Map接口称为键值对集合或者映射集合,其中的元素(entry)是以键值对(key-value)的形式存在. [2]Map 容器接口中提供了增.删.改.查的方式对集合进行操作. ...

  10. PAT (Basic Level) Practise (中文)- 1010. 一元多项式求导 (25)

    http://www.patest.cn/contests/pat-b-practise/1010 设计函数求一元多项式的导数.(注:xn(n为整数)的一阶导数为n*xn-1.) 输入格式:以指数递降 ...