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

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命令--jstack 工具

    一.介绍 jstack是java虚拟机自带的一种堆栈跟踪工具.jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项&qu ...

  2. HDU 2256Problem of Precision(矩阵快速幂)

    题意 求$(\sqrt{2} + \sqrt{3})^{2n} \pmod {1024}$ $n \leqslant 10^9$ Sol 看到题解的第一感受:这玩意儿也能矩阵快速幂??? 是的,它能q ...

  3. ABAP区别CLEAR、REFRESH、FREE

    CLEAR.REFRESH.FREE 内表:如果使用有表头行的内表,CLEAR 仅清除表格工作区域.例如 clear gs-school 清除工作区. 要重置整个内表而不清除表格工作区域,使用REFR ...

  4. echarts使用中的那些事儿( 三)

    饼图上的那些字与下面说明性的文字有些重合,该怎么缩小圆形的大小呢,还有它的位置,怎么让它向上一些或者向下一些: 有以下两个属性可以解决问题: radius : '55%', ------------这 ...

  5. linux命令行—《命令行快速入门》

    pwd print working directory 打印工作目录 hostname my computer's network name 电脑在网络中的名称 mkdir make director ...

  6. 洛谷 P3387 【模板】缩点

    题目背景 缩点+DP 题目描述 给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和. 允许多次经过一条边或者一个点,但是,重复经过的点,权值只 ...

  7. java面试题(杨晓峰)---第六讲谈谈动态代理是基于什么原理?

    我在编译时不知道,而在运行时知道,那么肯定在运行时给了提示,这个提示就是额外功.好处是可以重复利用相同代码. 代理模式:通过代理静默的解决一些与业务无关的问题,例如远程,安全,事物,日志,资源关闭,. ...

  8. python基础教程总结15——5 虚拟茶话会

    聊天服务器: 服务器能接受来自不同用户的多个连接: 允许用户同时(并行)操作: 能解释命令,例如,say或者logout: 容易拓展 套接字和端口: 套接字是一种使用标准UNIX文件描述符(file ...

  9. UVA 11468 Substring (AC自动机)

    用把失配边也加到正常边以后AC自动机,状态是长度递减的DAG,每次选一个不会匹配字符的转移. dp[u][L]表示当前在tire树上u结点长度还剩L时候不匹配的概率,根据全概率公式跑记忆化搜索. #i ...

  10. codeforces Gym 100286H Hell on the Markets

    紫书上面的题,队友做的,WA了freopen..爆了int... UVA 1614 - Hell on the Markets 奇怪的股市(贪心,结论)