Python源码学习:Python函数浅析-无参函数
发布日期:2021-07-25 13:04:26 浏览次数:10 分类:技术文章

本文共 9765 字,大约阅读时间需要 32 分钟。

Python源码分析

本文环境python2.5系列参考书籍<
>

本文会大致分析一下Python中的函数机制。在Python中,函数是一个比较重要的类型,在实现过程中主要参考了操作系统中的函数调用过程,把每个函数模拟成一段待执行的代码,在运行过程中调用,每一段执行的PyCodeObject都被包装在frame中,等待被调用执行,然后调用虚拟机调用执行,这个过程就是一个嵌套执行的过程。

分析

typedef struct {    PyObject_HEAD    PyObject *func_code;    /* A code object */   // PyCodeObject对象    PyObject *func_globals; /* A dictionary (other mappings won't do) */  //对应的函数全局变量    PyObject *func_defaults;    /* NULL or a tuple */ //函数的默认参数    PyObject *func_closure; /* NULL or a tuple of cell objects */    // 函数闭包实现    PyObject *func_doc;     /* The __doc__ attribute, can be anything */   // 函数文档    PyObject *func_name;    /* The __name__ attribute, a string object */     // 函数名称    PyObject *func_dict;    /* The __dict__ attribute, a dict or NULL */    // 函数属性    PyObject *func_weakreflist; /* List of weak references */    PyObject *func_module;  /* The __module__ attribute, can be anything */       /* Invariant:     *     func_closure contains the bindings for func_code->co_freevars, so     *     PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code)     *     (func_closure may be NULL if PyCode_GetNumFree(func_code) == 0).     */} PyFunctionObject;

由定义可知,PyFunctionObject也是一个PyObject,其中func_code是Python编译器在编译的时候就生成的对象,将Python的执行代码编译成了一个PyCodeObject对象,PyFunctionObject是Python解释器在执行字节码时,根据PyCodeObject代码生成的,并且包含函数执行时相应的全局变量。

先分析如下例子

def f():    print("hello world")f()

对应编译后的字节码为

1           0 LOAD_CONST               0 ()              3 MAKE_FUNCTION            0              6 STORE_NAME               0 (f)  4           9 LOAD_NAME                0 (f)             12 CALL_FUNCTION            0             15 POP_TOP                          16 LOAD_CONST               1 (None)             19 RETURN_VALUE

并且可以查看一下,code object f的结果为

2           0 LOAD_CONST               1 ('hello world')              3 PRINT_ITEM                        4 PRINT_NEWLINE                     5 LOAD_CONST               0 (None)              8 RETURN_VALUE

通过两段字节码的分析可知,该代码里面包含了两个PyCodeObject一个是运行的本身,另一个测试f对应的PyCodeObject。先分析本身的字节码执行,在LOAD_CONST对应的f的PyCodeObject后,直接调用了MAKE_FUNCTION,我们查看一些具体的执行流程。

v = POP(); /* code object */   //获取code            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);    // 根据传入参数的个数,将传入参数设置到tuple中                }                err = PyFunction_SetDefaults(x, v);  // 处理函数的默认参数                Py_DECREF(v);            }            PUSH(x);            break;

由此我们进一步分析PyFunction_New代码

PyObject *PyFunction_New(PyObject *code, PyObject *globals){    PyFunctionObject *op = PyObject_GC_New(PyFunctionObject,                        &PyFunction_Type);              // 申请函数对象的空间    static PyObject *__name__ = 0;    if (op != NULL) {                 PyObject *doc;        PyObject *consts;        PyObject *module;        op->func_weakreflist = NULL;        Py_INCREF(code);        op->func_code = code;                           // 设置函数对应的PyCodeObject        Py_INCREF(globals);        op->func_globals = globals;                     // 设置函数的全局变量        op->func_name = ((PyCodeObject *)code)->co_name;        // 设置函数的名称        Py_INCREF(op->func_name);        op->func_defaults = NULL; /* No default arguments */     // 设置函数的默认参数        op->func_closure = NULL;                           consts = ((PyCodeObject *)code)->co_consts;               // code中的常量        if (PyTuple_Size(consts) >= 1) {                          // 获取函数的doc            doc = PyTuple_GetItem(consts, 0);            if (!PyString_Check(doc) && !PyUnicode_Check(doc))                doc = Py_None;        }        else            doc = Py_None;        Py_INCREF(doc);        op->func_doc = doc;        op->func_dict = NULL;          op->func_module = NULL;               /* __module__: If module name is in globals, use it.           Otherwise, use None.        */        if (!__name__) {            __name__ = PyString_InternFromString("__name__");            if (!__name__) {                Py_DECREF(op);                return NULL;            }        }        module = PyDict_GetItem(globals, __name__);        if (module) {            Py_INCREF(module);            op->func_module = module;        }    }    else        return NULL;    _PyObject_GC_TRACK(op);    return (PyObject *)op;}

主要工作就是完成申请PyFunctionObject的空间大小,设置各个参数值。

至此,一个PyFunctionObject就完成创建。
接着就是LOAD_NAME f,此时就加载刚刚创建好的函数对象,然后调用CALL_FUNCTION,我们继续查看;

case CALL_FUNCTION:        {            PyObject **sp;            PCALL(PCALL_ALL);            sp = stack_pointer;#ifdef WITH_TSC            x = call_function(&sp, oparg, &intr0, &intr1);#else            x = call_function(&sp, oparg);#endif            stack_pointer = sp;            PUSH(x);            if (x != NULL)                continue;            break;        }

此时,将函数传入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;    /* Always dispatch PyCFunction first, because these are       presumed to be the most frequent callable object.    */    if (PyCFunction_Check(func) && nk == 0) {           // 检查func的类型,是否为cfunc        ...    } else {        if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {  // 检查func是否是类访问的方法            /* optimize access to bound methods */            PyObject *self = PyMethod_GET_SELF(func);            PCALL(PCALL_METHOD);            PCALL(PCALL_BOUND_METHOD);            Py_INCREF(self);            func = PyMethod_GET_FUNCTION(func);            Py_INCREF(func);            Py_DECREF(*pfunc);            *pfunc = self;            na++;            n++;        } 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);    }    /* Clear the stack of the function object.  Also removes           the arguments in case they weren't consumed already           (fast_function() and err_args() leave them on the stack).     */    while ((*pp_stack) > pfunc) {        w = EXT_POP(*pp_stack);        Py_DECREF(w);        PCALL(PCALL_POP);    }    return x;}

主要是先判断函数的类型,然后在根据函数的类型进行调用,在本例中,会调用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;    }    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));}

此时,调用fast_function就又调用解释器函数执行字节码,然后执行完成将结果返回,此时解释器执行的字节码为code f的字节码。

2           0 LOAD_CONST               1 ('hello world')              3 PRINT_ITEM                        4 PRINT_NEWLINE                     5 LOAD_CONST               0 (None)              8 RETURN_VALUE

该段字节码执行的源码分析,有兴趣可自行查看,执行完成后就是打印hello world,然后返回None值,然后通过RETURN_VALUE;

case RETURN_VALUE:            retval = POP();     // 弹出返回结果            why = WHY_RETURN;            goto fast_block_end;   // 跳转到fast_block_end

fast_block_end,该处代码就是判断当前执行流程中,是否有错误或者执行完成已经有返回结果了就中断循环,然后就执行到

/* pop frame */    exit_eval_frame:    Py_LeaveRecursiveCall();    tstate->frame = f->f_back;     // 将当前执行的帧设置为调用的帧    return retval;                // 返回调用执行的结果

至此,执行完成结果便会返回。

本次的无参函数的调用就分析完成。

转载地址:https://blog.csdn.net/qq_33339479/article/details/79768215 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Python源码学习:Python函数浅析-有参函数
下一篇:Python源码学习:内建类型简析并简析int对象

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年04月04日 12时30分36秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章