Python源码分析

本文环境python2.5系列
参考书籍<<Python源码剖析>>

继续上一篇无参函数的调用后,本文将分析Python中的有参函数的大致流程,在Python中主要的参数类型有四种;
1.位置参数,如f(a,b),a和b就称为位置参数;
2.键参数,如f(a=1),其中a=1就称为键参数;
3.扩展位置参数,如f(*list),其中调用为f(1,2)时,1和2就称为扩展位置参数;
4.扩展键参数,如f(**kw),其中调用为f(a=1,b=2),a=1和b=2就称为扩展键参数;
接下来就分析一下实现的流程。

分析

static PyObject *
call_function(PyObject ***pp_stack, int oparg
#ifdef WITH_TSC, uint64* pintr0, uint64* pintr1
#endif)
{int na = oparg & 0xff;         // 获取输入参数的个数int nk = (oparg>>8) & 0xff;    // 获取位置参数的个数int n = na + 2 * nk;           //  总大小,由于一个位置参数由key和value组成,所有乘2PyObject **pfunc = (*pp_stack) - n - 1;      // 获取函数对象...
}

在调用call_function函数的时候,传入了栈和操作数oparg,该oparg参数就是记录调用函数时的位置参数和键参数,并且该操作数是由两个字节构成,在低字节是记录位置参数的个数,在高字节是记录键参数的个数,所有理论上位置参数的个数和键参数的个数最大为256个,其中除了知道相应参数个数外,还有两个参数跟函数参数有关,co_argcount表示传入参数的个数和co_nlocals表示函数的局部变量,其实在有参函数中,传入的函数参数其实也算是函数的局部变量,在调用函数时,需要申请相应的内存空间,但是只知道co_nlocals时并不能知道其中有多少个参数是传入参数,所以需要两者配合才能知道调用函数时为参数申请多少的内存空间。

1.当调用

def f(a,b):pass

此时,当使用f(1,2)时,na=2,nk=0,n=2,co_argcount=2,co_nlocals=2;
当调用f(1,b=2)时,na=1,nk=1,n=3,co_argcount=2,co_nlocals=2;从本例中可以看出,虽然都是同样的调用结果但是参数却发生了变化,位置参数和键参数都反应了函数调用过程中的实际调用的参数个数,但为什么会有这种不同呢,不同的原因是,在调用f(1,b=2)时,函数调用了两次load_const,会将b压入参数栈中,这样就生成了对应的位置参数。
2.当调用

def f(a,b,*list):pass

此时,当调用f(1,2,3,4)时,na=4,nk=0,n=4,co_argcount=2,co_nlocals=3,在这里Python将*list作为了一个函数的局部变量保存,输入值得个数由a,b决定,所有在这里会发现函数的局部变量有三个,分别为a,b,list,并没有将list作为参数进行处理。因为co_argcount和co_nlocals是在编译的时候就确定了,所有该参数的处理在函数编译的过程就已经确定了。
3当调用

def f(a,b,**kw):pass

此时,当调用f(1,2,name=”1”,word=”2”)时,此时,na=2,nk=2,n=6,co_argcount=2,co_nlocals=3,所以键扩展参数也是通过局部变量实现的。
4当调用

def f(a,b,*list,**kw):pass

此时,当调用f(1,2,3,4,name=”1”)时,此时na=2,nk=1,n=4,co_argcount=2,co_nlocals=4,此时更明显的说明扩展位置参数和扩展键参数作为函数局部变量实现的机制。

接下来我们分析一下有参函数的调用过程。
如下为对应的Python源代码

def f(name, age):age += 5print(name, age)age = 5f("hello", age)

对应脚本相应的字节码为

  1           0 LOAD_CONST               0 (<code object f at 0x10a432630, file "test2.py", line 1>)3 MAKE_FUNCTION            06 STORE_NAME               0 (f)4           9 LOAD_CONST               1 (5)12 STORE_NAME               1 (age)6          15 LOAD_NAME                0 (f)18 LOAD_CONST               2 ('hello')21 LOAD_NAME                1 (age)24 CALL_FUNCTION            227 POP_TOP             28 LOAD_CONST               3 (None)31 RETURN_VALUE    

其中code f对应的字节码如下

  2           0 LOAD_FAST                1 (age)3 LOAD_CONST               1 (5)6 INPLACE_ADD         7 STORE_FAST               1 (age)3          10 LOAD_FAST                0 (name)13 LOAD_FAST                1 (age)16 BUILD_TUPLE              219 PRINT_ITEM          20 PRINT_NEWLINE       21 LOAD_CONST               0 (None)24 RETURN_VALUE   

对应的脚本字节码分析;

  6          15 LOAD_NAME                0 (f)18 LOAD_CONST               2 ('hello')21 LOAD_NAME                1 (age)24 CALL_FUNCTION            2

先压栈函数f,然后压栈’hello’对应的第一个函数参数,然后压栈age这个全局变量,最后调用call_function 2,此时这句与我们以往调用call_function 0不同,这次带了参数2,我们进入call_function进行分析

static PyObject *
call_function(PyObject ***pp_stack, int oparg
#ifdef WITH_TSC, uint64* pintr0, uint64* pintr1
#endif)
{int na = oparg & 0xff;         // 获取输入参数的个数int nk = (oparg>>8) & 0xff;    // 获取位置参数的个数int n = na + 2 * nk;           //  总大小,由于一个位置参数由key和value组成,所有乘2PyObject **pfunc = (*pp_stack) - n - 1;      // 获取函数对象PyObject *func = *pfunc;PyObject *x, *w;if (PyCFunction_Check(func) && nk == 0) {           // 检查func的类型,是否为cfunc...} else {if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {  // 检查func是否是类访问的方法...} elsePy_INCREF(func);READ_TIMESTAMP(*pintr0);if (PyFunction_Check(func))                                     // 检查是否是函数类型x = fast_function(func, pp_stack, n, na, nk);               // 处理快速方法elsex = do_call(func, pp_stack, na, nk);READ_TIMESTAMP(*pintr1);Py_DECREF(func);}...
}

当有参数传入时,先获取对应的输入参数,然后获取位置参数,键参数和参数的总大小,此时进过判断后会调用fast_function;

static PyObject *
fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
{PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);    // 获取函数的对应的字节码PyObject *globals = PyFunction_GET_GLOBALS(func);                // 获取函数的执行时的全局变量PyObject *argdefs = PyFunction_GET_DEFAULTS(func);               // 获取函数的默认参数PyObject **d = NULL;int nd = 0;PCALL(PCALL_FUNCTION);PCALL(PCALL_FAST_FUNCTION);  if (argdefs == NULL && co->co_argcount == n && nk==0 &&co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {   // 一般函数的执行过程PyFrameObject *f;                                              // 调用函数对应的帧PyObject *retval = NULL;                                       // 参数执行完成后的返回结果PyThreadState *tstate = PyThreadState_GET();                   // 获取当前线程的状态PyObject **fastlocals, **stack; int i;PCALL(PCALL_FASTER_FUNCTION);assert(globals != NULL);/* XXX Perhaps we should create a specializedPyFrame_New() that doesn't take locals, but doestake builtins without sanity checking them.*/assert(tstate != NULL);f = PyFrame_New(tstate, co, globals, NULL);                    // 生成一个新的帧对象if (f == NULL)return NULL;fastlocals = f->f_localsplus;                                  // 本地变量stack = (*pp_stack) - n;for (i = 0; i < n; i++) {Py_INCREF(*stack);fastlocals[i] = *stack++;}retval = PyEval_EvalFrameEx(f,0);                              // 调用解释器继续执行函数对应的字节码++tstate->recursion_depth;Py_DECREF(f);--tstate->recursion_depth;return retval;}...
}

由于此时有参数传入,但是参数的co_argcount等于输入的参数个数n,函数没有默认参数,此时就进入if判断中,按照一般函数流程进行执行,函数压栈中示意图;

| function f |
| 'hello'    |
|  age       |

在创建完,执行的帧后就执行如下

        fastlocals = f->f_localsplus;                                  // 本地变量stack = (*pp_stack) - n;                                       // 获取函数压入的参数for (i = 0; i < n; i++) {Py_INCREF(*stack);fastlocals[i] = *stack++;                                  // 将函数压入的参数依次放入fastlocals中}

将调用压入的函数参数依次放入fastlocals即f->f_localsplus中,再执行retval = PyEval_EvalFrameEx(f,0)时,会执行如下操作;

register PyObject **fastlocals
...
fastlocals = f->f_localsplus;

此时我们继续分析code f的字节码

  2           0 LOAD_FAST                1 (age)3 LOAD_CONST               1 (5)6 INPLACE_ADD         7 STORE_FAST               1 (age)3          10 LOAD_FAST                0 (name)13 LOAD_FAST                1 (age)16 BUILD_TUPLE              219 PRINT_ITEM          20 PRINT_NEWLINE       21 LOAD_CONST               0 (None)24 RETURN_VALUE  

此时调用了LOAD_FAST和STORE_FAST指令,我们可以看下相应指令对应的操作,其中LOAD_FAST如下;

        case LOAD_FAST:x = GETLOCAL(oparg);if (x != NULL) {Py_INCREF(x);PUSH(x);goto fast_next_opcode;}format_exc_check_arg(PyExc_UnboundLocalError,UNBOUNDLOCAL_ERROR_MSG,PyTuple_GetItem(co->co_varnames, oparg));break;#define GETLOCAL(i) (fastlocals[i])

我们可以看到该指令直接获取fastlocals中对应的值,此时oparg为1,就是对应age为5的值,当调用STORE_FAST时,

        case STORE_FAST:v = POP();SETLOCAL(oparg, v);goto fast_next_opcode;#define SETLOCAL(i, value)  do { PyObject *tmp = GETLOCAL(i); \GETLOCAL(i) = value; \Py_XDECREF(tmp); } while (0)

由对应的代码可知,此时获取存入的值,然后先获取获取一起存入的值,然后将该值设置到i对应的位置,把原值进行了替换。
由此可知,位置参数的函数调用都是通过了fastlocals这个属性,更新相应的值,然后函数内部的就会对相应的传入参数进行操作。

函数的默认参数传值,代码如下:

def f(a=1, b=2):print(a+b)f()
f(b=3)

脚本文件对应的字节码如下;

  1           0 LOAD_CONST               0 (1)3 LOAD_CONST               1 (2)6 LOAD_CONST               2 (<code object f at 0x106c83630, file "test3.py", line 1>)9 MAKE_FUNCTION            212 STORE_NAME               0 (f)4          15 LOAD_NAME                0 (f)18 CALL_FUNCTION            021 POP_TOP             5          22 LOAD_NAME                0 (f)25 LOAD_CONST               3 ('b')28 LOAD_CONST               4 (3)31 CALL_FUNCTION          25634 POP_TOP             35 LOAD_CONST               5 (None)38 RETURN_VALUE

函数f对应的字节码如下;

  2           0 LOAD_FAST                0 (a)3 LOAD_FAST                1 (b)6 BINARY_ADD          7 PRINT_ITEM          8 PRINT_NEWLINE       9 LOAD_CONST               0 (None)12 RETURN_VALUE 

我们先从脚本文件字节码进行分析;

  1           0 LOAD_CONST               0 (1)3 LOAD_CONST               1 (2)6 LOAD_CONST               2 (<code object f at 0x106c83630, file "test3.py", line 1>)9 MAKE_FUNCTION            212 STORE_NAME               0 (f)

在生成函数f之前,需要先进行函数值得压栈入栈操作,将两个默认参数默认值进行了压栈,从左至右依次压入了1,2,然后压入f的字节码之后就调用了
MAKE_FUNCTION,并且还传入了2这个参数,我们分析一下MAKE_FUNCTION;

            v = POP(); /* code object */x = PyFunction_New(v, f->f_globals);Py_DECREF(v);/* XXX Maybe this should be a separate opcode? */if (x != NULL && oparg > 0) {v = PyTuple_New(oparg);       // 生成一个元组存放传入默认值if (v == NULL) {Py_DECREF(x);x = NULL;break;}while (--oparg >= 0) {w = POP();PyTuple_SET_ITEM(v, oparg, w);  // 依次按照从左至右的顺序依次将值设置到生成的参数元组中}err = PyFunction_SetDefaults(x, v); // 设置到函数的默认参数中Py_DECREF(v);}PUSH(x);break;

由此可见,默认值参数是将默认值依次压入到一个元组中,并设置到f对应的默认参数值中,在生成函数之后,继续查看脚本文件字节码调用了

  4          15 LOAD_NAME                0 (f)18 CALL_FUNCTION            021 POP_TOP   

调用了CALL_FUNCTION,由于此处函数都是普通函数调用,所有执行函数会知道到fast_function中,我们分析一下代码;

static PyObject *
fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
{PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);    // 获取函数的对应的字节码PyObject *globals = PyFunction_GET_GLOBALS(func);                // 获取函数的执行时的全局变量PyObject *argdefs = PyFunction_GET_DEFAULTS(func);               // 获取函数的默认参数PyObject **d = NULL;int nd = 0;PCALL(PCALL_FUNCTION);PCALL(PCALL_FAST_FUNCTION);  if (argdefs == NULL && co->co_argcount == n && nk==0 &&co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {   // 一般无默认参数函数的执行过程....}if (argdefs != NULL) {d = &PyTuple_GET_ITEM(argdefs, 0);     // 获取默认参数的第一个值nd = ((PyTupleObject *)argdefs)->ob_size; // 获取默认参数的大小}return PyEval_EvalCodeEx(co, globals,(PyObject *)NULL, (*pp_stack)-n, na,(*pp_stack)-2*nk, nk, d, nd,PyFunction_GET_CLOSURE(func));  // 调用该方法处理
}

此处函数执行会进入PyEval_EvalCodeEx执行,我们查看代码, 由于na=0,nk=0,n=0所有传入相应的参数值na, n, nk也都是0;

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;if (globals == NULL) {PyErr_SetString(PyExc_SystemError,"PyEval_EvalCodeEx: NULL globals");return NULL;}assert(tstate != NULL);assert(globals != NULL);f = PyFrame_New(tstate, co, globals, locals);          // 生成一个运行的帧栈if (f == NULL)return NULL;fastlocals = f->f_localsplus;                          // 获取函数执行的局部变量      freevars = f->f_localsplus + co->co_nlocals;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) {int m = co->co_argcount - defcount;   // 默认参数总数减去默认参数,在本例中都是2,所有m为0for (i = argcount; i < m; i++) {if (GETLOCAL(i) == NULL) {PyErr_Format(PyExc_TypeError,"%.200s() takes %s %d ""%sargument%s (%d given)",PyString_AsString(co->co_name),((co->co_flags & CO_VARARGS) ||defcount) ? "at least": "exactly",m, kwcount ? "non-keyword " : "",m == 1 ? "" : "s", i);goto fail;}}if (n > m)        // 由于n=0,m=0,所有i=0i = n - m;  elsei = 0;for (; i < defcount; i++) {if (GETLOCAL(m+i) == NULL) {PyObject *def = defs[i];     // 获取默认参数的值Py_INCREF(def);SETLOCAL(m+i, def);          // 将默认参数设置到fastlocals对应的位置中去}}}...retval = PyEval_EvalFrameEx(f,0);...}

默认参数的值会被设置到对应的fastlocals中去,然后根据参数的索引值来访问默认参数,在code f中直接调用load_fast通过索引值去访问对应的函数值;

当无参函数调用完成后,我们继续执行脚本文件的字节码;

  5          22 LOAD_NAME                0 (f)25 LOAD_CONST               3 ('b')28 LOAD_CONST               4 (3)31 CALL_FUNCTION          25634 POP_TOP             35 LOAD_CONST               5 (None)38 RETURN_VALUE

先获取产量字段中,对应的常量值,然后压入栈中,然后就调用CALL_FUNCTION 256;同样函数会执行到PyEval_EvalCodeEx, 此时对应的na=0,nk=1,n=2,co_argcount=2,co_nlocals=2,此时,我们继续查看该函数;

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;if (globals == NULL) {PyErr_SetString(PyExc_SystemError,"PyEval_EvalCodeEx: NULL globals");return NULL;}assert(tstate != NULL);assert(globals != NULL);f = PyFrame_New(tstate, co, globals, locals);          // 生成一个运行的帧栈if (f == NULL)return NULL;fastlocals = f->f_localsplus;                          // 获取函数执行的局部变量      freevars = f->f_localsplus + co->co_nlocals;if (co->co_argcount > 0 ||co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)) {...for (i = 0; i < kwcount; i++) {           // 遍历键参数并确定输入参数是否在键参数中出现PyObject *keyword = kws[2*i];         // 获取对应的keyPyObject *value = kws[2*i + 1];       // 对应的valueint j;if (keyword == NULL || !PyString_Check(keyword)) {PyErr_Format(PyExc_TypeError,"%.200s() keywords must be strings",PyString_AsString(co->co_name));goto fail;}/* XXX slow -- speed up using dictionary? */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;}...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);   // 将参数设置到生成的kwdict中}else {                        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);         // 替换相应位置的值,依次到达将默认参数值替换的效果}...for (; i < defcount; i++) {if (GETLOCAL(m+i) == NULL) {     // 由于此时b索引值对应有3这个值,此时该值就不会被设置,依次达到了默认值更新PyObject *def = defs[i];   Py_INCREF(def);SETLOCAL(m+i, def);}...
}

在执行过程中,由于将压入了键参数,所以此时,在函数中,先找出f中对应的键参数的变量b,当找到键参数变量b中时,此时就找到对应的默认参数对应的索引值,然后在设置函数默认参数时,如果监测到该位置有值,就不再更新,如果没有值,就将默认参数设置其中。
至此默认参数的解析完成。

扩展位置参数和扩展键参数
脚本示例如下

def f(a, *lst, **kw):passf(1, 2, 3, name="name", ps="123")

此时脚本对应的字节码如下

  1           0 LOAD_CONST               0 (<code object f at 0x10d9b1630, file "test4.py", line 1>)3 MAKE_FUNCTION            06 STORE_NAME               0 (f)4           9 LOAD_NAME                0 (f)12 LOAD_CONST               1 (1)15 LOAD_CONST               2 (2)18 LOAD_CONST               3 (3)21 LOAD_CONST               4 ('name')24 LOAD_CONST               4 ('name')27 LOAD_CONST               5 ('ps')30 LOAD_CONST               6 ('123')33 CALL_FUNCTION          51536 POP_TOP             37 LOAD_CONST               7 (None)40 RETURN_VALUE 

经过以上分析,可知此时,na=3, nk=2, n=7, co_argcount=1, co_nlocals=3,由此可以推断此时的lst,kw都处理成了函数的静态变量,当调用函数时还是会执行到PyEval_EvalCodeEx,我们分析一下此时的执行流程;

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;if (globals == NULL) {PyErr_SetString(PyExc_SystemError,"PyEval_EvalCodeEx: NULL globals");return NULL;}assert(tstate != NULL);assert(globals != NULL);f = PyFrame_New(tstate, co, globals, locals);          // 生成一个运行的帧栈if (f == NULL)return NULL;fastlocals = f->f_localsplus;                          // 获取函数执行的局部变量      freevars = f->f_localsplus + co->co_nlocals;if (co->co_argcount > 0 ||co->co_flags & (CO_VARARGS | CO_VARKEYWORDS)) {int i;int n = argcount;PyObject *kwdict = NULL;if (co->co_flags & CO_VARKEYWORDS) {   // 判断是否有扩展键参数kwdict = PyDict_New();             // 生成键参数存放的字典if (kwdict == NULL)goto fail;i = co->co_argcount;               // 获取输入参数的个数if (co->co_flags & CO_VARARGS)i++;                           // 如果此时也有扩展位置参数,则扩展键参数需要放到扩展位置参数后面一位SETLOCAL(i, kwdict);               // 将对应的位置放入生成的字典}if (argcount > co->co_argcount) {      // 此时argcount=3,co_argcount=1if (!(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;               // 将n设置为1}for (i = 0; i < n; i++) {             // 此时n=1,在第一个位置将第一值放入,此时第一个值为1x = args[i];Py_INCREF(x);SETLOCAL(i, x);}if (co->co_flags & CO_VARARGS) {            // 此时有位置,会生成一个tuple来存储传入的值u = PyTuple_New(argcount - n);          // 此时生成2个大小的元组if (u == NULL)goto fail;SETLOCAL(co->co_argcount, u);          // 设置1的位置为扩展位置参数的存放地址for (i = n; i < argcount; i++) {x = args[i];Py_INCREF(x);PyTuple_SET_ITEM(u, i-n, x);       // 将剩下的2,3依次压入元组中}}for (i = 0; i < kwcount; i++) {            // 处理键参数PyObject *keyword = kws[2*i];PyObject *value = kws[2*i + 1];int j;if (keyword == NULL || !PyString_Check(keyword)) {PyErr_Format(PyExc_TypeError,"%.200s() keywords must be strings",PyString_AsString(co->co_name));goto fail;}/* XXX slow -- speed up using dictionary? */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;}/* Check errors from Compare */if (PyErr_Occurred())goto fail;if (j >= co->co_argcount) {        // 此时j为1,co_argcount为1if (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); // 将获取的键与对应的键值设置到存储的kwdict中}...}

至此,扩展位置参数和扩展键参数已经执行完成执行完成后对应的内存结构如下

|   1    |   lst   |  kw   ||        |(2,3)    {'name':'name','ps':'123'}

此时再函数内部访问时直接就是通过lst,kw对应的f_localsplus中的索引位置直接访问,至此有关函数参数的传递已经分析完成。

Python源码学习:Python函数浅析-有参函数相关推荐

  1. Python源码学习:Python函数浅析-函数闭包

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 上一篇分析了函数参数的分析后,本文分析函数闭包的实现.函数闭包即函数定义和函数表达式 ...

  2. Python源码学习:Python函数浅析-无参函数

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 本文会大致分析一下Python中的函数机制.在Python中,函数是一个比较重要的类 ...

  3. Python源码学习:Python类机制分析-用户自定义类

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 上一文,分析了Python在启动初始化时,对内置类的一个基本的初始化流程,本文就简析 ...

  4. Python源码学习:Python类机制分析

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 本文主要分析Python中类时如何实现的,在Python中,一切都是对象:任何对象都 ...

  5. Python源码学习:启动流程简析

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> Python简介: python主要是动态语言,虽然Python语言也有编译,生成中 ...

  6. python源码学习_【Python学习】Python源码阅读(一)

    最近想读读Python源码,任何东西学习方法基本都是一样的,先从总体框架进行了解,再从自己侧重的方面逐步深入. 1. Python总体架构 左边是Python提供的大量的模块.库以及用户自定义的模块. ...

  7. Python 源码学习:类型和对象

    Python 是一门解释型,动态类型,多范式的编程语言,当我们从 python.org 下载并安装运行 Python 的某个分发版本时,我们实际上是在运行由 C 语言编写的 CPython ,除此之外 ...

  8. Python源码学习笔记:Python程序执行过程与字节码

    Python程序执行过程与字节码 注:本篇是根据教程学习记录的笔记,部分内容与教程是相同的,因为转载需要填链接,但是没有,所以填的原创,如果侵权会直接删除. 问题: 我们每天都要编写一些Python程 ...

  9. Python源码学习:多线程实现机制

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 本文分析Python中的多线程机制,主要通过一个多线程的脚本来分析多线程的基本操作与 ...

最新文章

  1. CSS-3 Animation 的使用
  2. 矿泉水功能突破口-丰收节交易会·李喜贵:遵义谋定水产业
  3. 关于CI的服务器与最佳实践,这里有一些思考 1
  4. css3动画事件—webkitAnimationEnd
  5. 少年,这有套《街霸2》AI速成心法,想传授于你……
  6. js将数字转成大写中文
  7. 机器学习之 决策树(Decision Tree)
  8. 分享下我的 netbeans 的配色方案
  9. 蔡学镛:文档模板,天使或恶魔?
  10. 惯性导航(IMU)误差分析
  11. CocosCreator Effect (Shader) - 反九宫格就(Sliced)补偿
  12. 宝付大叔的家书,满满的父爱
  13. 【Vue3.0移动端项目--旅游网】-- 房屋详情页创建以及房屋详情图片展示
  14. switch分支语句注意事项及注册界面的使用思路
  15. windows服务器系统监控
  16. 机器人涂装工程师岗位英文缩写_公司里各职位名称的英文缩写
  17. 结合新的实际深化对自主创新的认识
  18. LiveVideoStackCon 2022 上海站优秀出品人、讲师及志愿者
  19. 七牛云招聘~视频云产品经理
  20. python爬取知乎文章_大佬分享Python编程实现爬取知乎用户信息的例子

热门文章

  1. 介绍如何用 Python 来绘制高清的交互式地图,建议收藏
  2. CT片居然可以这么玩:用头部CT断层扫描片复原三维头像
  3. 知乎联合清华:开放国内最大个性化推荐实际交互数据集
  4. “崩溃!我再也不搞 AI 了”谷歌 AI 专家:别让你的方法打败你!
  5. 抗击新冠肺炎,如何进行实时动态时序图谱建模与分析?
  6. NLP学习思维导图,非常的全面和清晰
  7. 终于把微软BING搜索-SPTAG算法的原理搞清了
  8. 一文看懂深度学习发展史和常见26个模型
  9. 2017清华本科生特等奖得主出炉,AI学霸乔明达获奖
  10. 尽快卸载这两款恶意浏览器插件!已有近 50 万用户安装