当前位置:   article > 正文

Python源码学习:Python函数浅析-有参函数_pyfunction_check

pyfunction_check

Python源码分析

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

继续上一篇无参函数的调用后,本文将分析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组成,所有乘2
    PyObject **pfunc = (*pp_stack) - n - 1;      // 获取函数对象
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

1.当调用

def f(a,b):
    pass
  • 1
  • 2

此时,当使用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
  • 1
  • 2

此时,当调用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
  • 1
  • 2

此时,当调用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
  • 1
  • 2

此时,当调用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 += 5
    print(name, age)

age = 5

f("hello", age)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

对应脚本相应的字节码为

  1           0 LOAD_CONST               0 (<code object f at 0x10a432630, file "test2.py", line 1>)
              3 MAKE_FUNCTION            0
              6 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            2
             27 POP_TOP             
             28 LOAD_CONST               3 (None)
             31 RETURN_VALUE    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

其中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              2
             19 PRINT_ITEM          
             20 PRINT_NEWLINE       
             21 LOAD_CONST               0 (None)
             24 RETURN_VALUE   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

对应的脚本字节码分析;

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

先压栈函数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组成,所有乘2
    PyObject **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是否是类访问的方法
            ...
        } else
            Py_INCREF(func);
        READ_TIMESTAMP(*pintr0);
        if (PyFunction_Check(func))                                     // 检查是否是函数类型
            x = fast_function(func, pp_stack, n, na, nk);               // 处理快速方法
        else
            x = do_call(func, pp_stack, na, nk);
        READ_TIMESTAMP(*pintr1);
        Py_DECREF(func);
    }
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

当有参数传入时,先获取对应的输入参数,然后获取位置参数,键参数和参数的总大小,此时进过判断后会调用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 specialized
           PyFrame_New() that doesn't take locals, but does
           take 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;
    }
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

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

| function f |
| 'hello'    |
|  age       |
  • 1
  • 2
  • 3

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

        fastlocals = f->f_localsplus;                                  // 本地变量
        stack = (*pp_stack) - n;                                       // 获取函数压入的参数

        for (i = 0; i < n; i++) {
            Py_INCREF(*stack);
            fastlocals[i] = *stack++;                                  // 将函数压入的参数依次放入fastlocals中
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

register PyObject **fastlocals
...
fastlocals = f->f_localsplus;
  • 1
  • 2
  • 3

此时我们继续分析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              2
             19 PRINT_ITEM          
             20 PRINT_NEWLINE       
             21 LOAD_CONST               0 (None)
             24 RETURN_VALUE  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

此时调用了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])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

我们可以看到该指令直接获取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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

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

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

f()
f(b=3)
  • 1
  • 2
  • 3
  • 4
  • 5

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

  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            2
             12 STORE_NAME               0 (f)

  4          15 LOAD_NAME                0 (f)
             18 CALL_FUNCTION            0
             21 POP_TOP             

  5          22 LOAD_NAME                0 (f)
             25 LOAD_CONST               3 ('b')
             28 LOAD_CONST               4 (3)
             31 CALL_FUNCTION          256
             34 POP_TOP             
             35 LOAD_CONST               5 (None)
             38 RETURN_VALUE
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

函数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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

  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            2
             12 STORE_NAME               0 (f)
  • 1
  • 2
  • 3
  • 4
  • 5

在生成函数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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

  4          15 LOAD_NAME                0 (f)
             18 CALL_FUNCTION            0
             21 POP_TOP   
  • 1
  • 2
  • 3

调用了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));  // 调用该方法处理
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

此处函数执行会进入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为0
            for (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=0
                i = n - m;  
            else
                i = 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);
        ...
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

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

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

  5          22 LOAD_NAME                0 (f)
             25 LOAD_CONST               3 ('b')
             28 LOAD_CONST               4 (3)
             31 CALL_FUNCTION          256
             34 POP_TOP             
             35 LOAD_CONST               5 (None)
             38 RETURN_VALUE
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

先获取产量字段中,对应的常量值,然后压入栈中,然后就调用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];         // 获取对应的key
            PyObject *value = kws[2*i + 1];       // 对应的value
            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;
            }
            ...
            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);
                }
        ... 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

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

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

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

f(1, 2, 3, name="name", ps="123")
  • 1
  • 2
  • 3
  • 4

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

  1           0 LOAD_CONST               0 (<code object f at 0x10d9b1630, file "test4.py", line 1>)
              3 MAKE_FUNCTION            0
              6 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          515
             36 POP_TOP             
             37 LOAD_CONST               7 (None)
             40 RETURN_VALUE 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

经过以上分析,可知此时,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=1
            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;               // 将n设置为1
        }
        for (i = 0; i < n; i++) {             // 此时n=1,在第一个位置将第一值放入,此时第一个值为1
            x = 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为1
                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中
            }
            ...
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/199105
推荐阅读
相关标签
  

闽ICP备14008679号