美文网首页
大师兄的Python源码学习笔记(十九): 虚拟机中的函数机制(

大师兄的Python源码学习笔记(十九): 虚拟机中的函数机制(

作者: superkmi | 来源:发表于2021-06-07 06:38 被阅读0次

    大师兄的Python源码学习笔记(十八): 虚拟机中的控制流(五)
    大师兄的Python源码学习笔记(二十): 虚拟机中的函数机制(二)

    一、关于PyFunctionObject对象

    • 在Python中,PyCodeObject对象是一段Python源代码的静态表示,在源代码经过编译后,一个Code Block会产生一个且只有一个PyCodeObject,它包含了这个Code Block的静态信息。
    • PyFunctionObject对象则是在Python代码运行时动态产生的(执行def语句时)。
    • PyFunctionObject对象中会包含这个函数的静态信息(*func_code中),此外还会包含一些函数在执行时必须的动态信息(上下文信息)。
    Include\funcobject.h
    
    typedef struct {
        PyObject_HEAD
        PyObject *func_code;        /* A code object, the __code__ attribute */
        PyObject *func_globals;     /* A dictionary (other mappings won't do) */
        PyObject *func_defaults;    /* NULL or a tuple */
        PyObject *func_kwdefaults;  /* NULL or a dict */
        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 */
        PyObject *func_annotations; /* Annotations, a dict or NULL */
        PyObject *func_qualname;    /* The qualified name */
    
        /* 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;
    
    成员 说明
    *func_code 对应函数编译后的PyCodeObject对象
    *func_globals 函数运行时的global名字空间
    *func_defaults 默认参数(tuple)
    *func_kwdefaults 默认参数(dict)
    *func_closure 用于实现闭包的cell对象元祖
    *func_doc 函数的文档
    *func_name 函数名(__name__属性)
    *func_dict 函数的__dict__属性
    *func_weakreflist 弱引用
    *func_module 函数的__module__
    *func_annotations 函数的注释
    *func_qualname 函数的qualified name
    • 对于一段代码,其对应的PyCodeObject对象只有一个,而对应的PyFunctionObject对象却可能有很多个,每次调用函数都会创建PyFunctionObject对象。
    • 而所有PyFunctionObject对象都会关联到PyCodeObject对象。

    二、无参函数的调用

    1.1 函数对象的创建
    • 无参函数调用是最简单的函数调用形式,创建一个简单的案例:
    demo.py
    
    def f():
        ...
    f()
    
    • 对应的指令字节码如下:
      1           0 LOAD_CONST               0 (<code object f at 0x000001F28B9D8A50, file "demo.py", line 1>)
                  2 LOAD_CONST               1 ('f')
                  4 MAKE_FUNCTION            0
                  6 STORE_NAME               0 (f)
    
      3           8 LOAD_NAME                0 (f)
                 10 CALL_FUNCTION            0
                 12 POP_TOP
                 14 LOAD_CONST               2 (None)
                 16 RETURN_VALUE
    
       consts
          code
      2           0 LOAD_CONST               0 (None)
                  2 RETURN_VALUE
    
    • 从上面的代码可以看出代码在经过编译后,产生了两个PyCodeObject对象,常规部分对应demo.py,code部分对应f()。
    • 按照顺序从上往下看,分别是:
    • 声明函数
     1           0 LOAD_CONST               0 (<code object f at 0x000001F28B9D8A50, file "demo.py", line 1>)
                 2 LOAD_CONST               1 ('f')
                 4 MAKE_FUNCTION            0
                 6 STORE_NAME               0 (f)
    
    • 调用函数
     3           8 LOAD_NAME                0 (f)
                10 CALL_FUNCTION            0
                12 POP_TOP
                14 LOAD_CONST               2 (None)
                16 RETURN_VALUE
    
    • 实现函数
     2           0 LOAD_CONST               0 (None)
                 2 RETURN_VALUE
    
    • 从上面的顺序可以看出,声明函数实现函数调用函数分割成两个不同的PyCodeObject,而在Python代码中,他们应该是一个完整的整体。
    • 这是因为在Python中函数也是对象,在调用函数之前必须先创建这个函数对象,而这里的创建工作是通过def f()这条代码完成的,他对应了字节码指令Make_FUNCTION
    ceval.c
    
            TARGET(MAKE_FUNCTION) {
                PyObject *qualname = POP();
                PyObject *codeobj = POP();
                PyFunctionObject *func = (PyFunctionObject *)
                    PyFunction_NewWithQualName(codeobj, f->f_globals, qualname);
    
                Py_DECREF(codeobj);
                Py_DECREF(qualname);
                if (func == NULL) {
                    goto error;
                }
    
                if (oparg & 0x08) {
                    assert(PyTuple_CheckExact(TOP()));
                    func ->func_closure = POP();
                }
                if (oparg & 0x04) {
                    assert(PyDict_CheckExact(TOP()));
                    func->func_annotations = POP();
                }
                if (oparg & 0x02) {
                    assert(PyDict_CheckExact(TOP()));
                    func->func_kwdefaults = POP();
                }
                if (oparg & 0x01) {
                    assert(PyTuple_CheckExact(TOP()));
                    func->func_defaults = POP();
                }
    
                PUSH((PyObject *)func);
                DISPATCH();
            }
    
    • 在这里,虚拟机将f对应的qualnamePyCodeObject对象弹出,与当前PyFrameObject对象维护的global名字空间f_globals对象为参数,通过PyFunction_NewWithQualName创建了一个新的PyFunctionObject,而这个f_globals将成为函数f在运行时的global名字空间。
    Objects\funcobject.c
    
    PyObject *
    PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)
    {
        PyFunctionObject *op;
        PyObject *doc, *consts, *module;
        static PyObject *__name__ = NULL;
    
        if (__name__ == NULL) {
            __name__ = PyUnicode_InternFromString("__name__");
            if (__name__ == NULL)
                return NULL;
        }
    
        op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);
        if (op == NULL)
            return NULL;
    
        op->func_weakreflist = NULL;
        Py_INCREF(code);
        op->func_code = code;
        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_kwdefaults = NULL; /* No keyword only defaults */
        op->func_closure = NULL;
    
        consts = ((PyCodeObject *)code)->co_consts;
        if (PyTuple_Size(consts) >= 1) {
            doc = PyTuple_GetItem(consts, 0);
            if (!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;
        op->func_annotations = NULL;
    
        /* __module__: If module name is in globals, use it.
           Otherwise, use None. */
        module = PyDict_GetItem(globals, __name__);
        if (module) {
            Py_INCREF(module);
            op->func_module = module;
        }
        if (qualname)
            op->func_qualname = qualname;
        else
            op->func_qualname = op->func_name;
        Py_INCREF(op->func_qualname);
    
        _PyObject_GC_TRACK(op);
        return (PyObject *)op;
    }
    
    • 在创建PyFunctionObject对象后,MAKE_FUNCTION还会进行一些处理函数参数的动作,并压入运行时栈中,这里由于是无参函数暂时跳过。
    1.2 函数调用
    • 函数调用从CALL_FUNCTION指令开始:
    ceval.c
    
            PREDICTED(CALL_FUNCTION);
            TARGET(CALL_FUNCTION) {
                PyObject **sp, *res;
                sp = stack_pointer;
                res = call_function(&sp, oparg, NULL);
                stack_pointer = sp;
                PUSH(res);
                if (res == NULL) {
                    goto error;
                }
                DISPATCH();
            }
    
    • 虚拟机在获得了当前运行时栈栈顶指针后,直接调用了call_function
    ceval.c
    
    Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION
    call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames)
    {
        PyObject **pfunc = (*pp_stack) - oparg - 1;
        PyObject *func = *pfunc;
        PyObject *x, *w;
        Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
        Py_ssize_t nargs = oparg - nkwargs;
        PyObject **stack = (*pp_stack) - nargs - nkwargs;
    
        /* Always dispatch PyCFunction first, because these are
           presumed to be the most frequent callable object.
        */
        if (PyCFunction_Check(func)) {
            PyThreadState *tstate = PyThreadState_GET();
            C_TRACE(x, _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames));
        }
        else if (Py_TYPE(func) == &PyMethodDescr_Type) {
            PyThreadState *tstate = PyThreadState_GET();
            if (nargs > 0 && tstate->use_tracing) {
                /* We need to create a temporary bound method as argument
                   for profiling.
    
                   If nargs == 0, then this cannot work because we have no
                   "self". In any case, the call itself would raise
                   TypeError (foo needs an argument), so we just skip
                   profiling. */
                PyObject *self = stack[0];
                func = Py_TYPE(func)->tp_descr_get(func, self, (PyObject*)Py_TYPE(self));
                if (func != NULL) {
                    C_TRACE(x, _PyCFunction_FastCallKeywords(func,
                                                             stack+1, nargs-1,
                                                             kwnames));
                    Py_DECREF(func);
                }
                else {
                    x = NULL;
                }
            }
            else {
                x = _PyMethodDescr_FastCallKeywords(func, stack, nargs, kwnames);
            }
        }
        else {
            if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
                /* Optimize access to bound methods. Reuse the Python stack
                   to pass 'self' as the first argument, replace 'func'
                   with 'self'. It avoids the creation of a new temporary tuple
                   for arguments (to replace func with self) when the method uses
                   FASTCALL. */
                PyObject *self = PyMethod_GET_SELF(func);
                Py_INCREF(self);
                func = PyMethod_GET_FUNCTION(func);
                Py_INCREF(func);
                Py_SETREF(*pfunc, self);
                nargs++;
                stack--;
            }
            else {
                Py_INCREF(func);
            }
    
            if (PyFunction_Check(func)) {
                x = _PyFunction_FastCallKeywords(func, stack, nargs, kwnames);
            }
            else {
                x = _PyObject_FastCallKeywords(func, stack, nargs, kwnames);
            }
            Py_DECREF(func);
        }
    
        assert((x != NULL) ^ (PyErr_Occurred() != NULL));
    
        /* Clear the stack of the function object. */
        while ((*pp_stack) > pfunc) {
            w = EXT_POP(*pp_stack);
            Py_DECREF(w);
        }
    
        return x;
    }
    
    • 在上面代码中,首先会通过PyCFunction_Check判断是不是C函数,如果是则进入快车道。
    • 如果是普通函数,则通过PyFunction_Check检查后,会进入分支_PyFunction_FastCallKeywords_PyObject_FastCallKeywords
    • 但不管进入哪个分支,最终虚拟机会调用PyEval_EvalFrameEx
    ceval.c
    
    PyObject *
    PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
    {
        PyThreadState *tstate = PyThreadState_GET();
        return tstate->interp->eval_frame(f, throwflag);
    }
    
    • PyEval_EvalFrameEx开始,虚拟机进入了函数调用状态:创建新的帧栈->在新的帧栈中执行代码。
    • 在最终通过PyEval_EvalFrameEx时,PyFunctionObject对象的影响已经消失,真正对新帧栈产生影响的是在PyFunctionObject中存储的PyCodeObject对象和global名字空间。
    1.3 函数执行时的名字空间
    • 在执行demo.py时的字节码指令和执行f的字节码指令时的名字空间是同一个名字空间。
    • 这表示在函数f中可以直接使用f函数外的符号。
    • 此外在字节码指令STORENAME处,会将符号f的放入f_local中,也就是同时将f放进f_globals中,得以在函数f中实现递归。
    ceval.c
    
            case STORE_NAME:
            {
                PyObject *names = f->f_code->co_names;
                PyObject *name = GETITEM(names, oparg);
                PyObject *locals = f->f_locals;
                if (locals && PyDict_CheckExact(locals) &&
                    PyDict_GetItem(locals, name) == v) {
                    if (PyDict_DelItem(locals, name) != 0) {
                        PyErr_Clear();
                    }
                }
                break;
            }
    

    相关文章

      网友评论

          本文标题:大师兄的Python源码学习笔记(十九): 虚拟机中的函数机制(

          本文链接:https://www.haomeiwen.com/subject/ykjssltx.html