1. 程式人生 > >Python 原始碼分析之函式機制

Python 原始碼分析之函式機制

在 python 中函式也是一個物件

typedef struct {
    PyObject_HEAD
    PyObject *func_code;    /*  函式編譯之後的 PyCodeObject, 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, 函式編譯之後,程式碼段的第一個常量 */ 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;

PyCodeObject 是一個靜態程式碼塊的描述,說靜態是應該在編譯的時候就可以知道該
程式碼塊有哪些內容。比如常量表,符號表,區域性變數等等,我們一般都針對的是
PyCodeObject。

而 PyFunctionObject 是動態的,這個物件是定義 def 的函式之後,每個一個函式
都會對應一個 PyFunctionObject,其中 func_code 儲存的就是該函式的程式碼相關
部分,除此之外,還有 globals, default 等等,一般是動態決定的。 還要需要
注意的是,對於一個函式 f,在執行過程中,可能在多處呼叫,就會建立多個
PyFunctionObject 物件,而每個物件的 func_code 都指向同一 PyCodeObject。

所以,對於一個 PyFunctionObject 它既包含靜態部分,如 func_code, 也包含
動態部分,比如函式引數。

#!/usr/bin/env python
# encoding: utf-8

def test():
    a = 1

test()

編譯之後

magic b'330d0d0a'
moddate b'c075345b' (Thu Jun 28 13:44:32 2018)
files sz 604
code
   argcount 0
   nlocals 0
   stacksize 2
   flags 0040
   code b'6400640184005a0065008300010064025300'
  4           0 LOAD_CONST               0 (<code object test at 0x10ca48ae0, file "/tmp/a.py", line 4>)
              2 LOAD_CONST               1 ('test')
              4 MAKE_FUNCTION            0           //建立 PyFunctionObject 併入棧
              6 STORE_NAME               0 (test)    //將 PyFunctionObject 儲存到 test

  7           8 LOAD_NAME                0 (test)
             10 CALL_FUNCTION            0
             12 POP_TOP
             14 LOAD_CONST               2 (None)
             16 RETURN_VALUE
   consts
      code
         argcount 0
         nlocals 1
         stacksize 1
         flags 0043
         code b'64017d0064005300'
  5           0 LOAD_CONST               1 (1)
              2 STORE_FAST               0 (a)
              4 LOAD_CONST               0 (None)
              6 RETURN_VALUE
         consts
            None
            1
         names ()
         varnames ('a',)
         freevars ()
         cellvars ()
         filename '/tmp/a.py'
         name 'test'
         firstlineno 4
         lnotab b'0001'
      'test'
      None
   names ('test',)
   varnames ()
   freevars ()
   cellvars ()
   filename '/tmp/a.py'
   name '<module>'
   firstlineno 4
   lnotab b'0803'

由編譯可知產生兩個程式碼段,一個是 a.py 對應的 PyCodeObject,一個是函式 test 對應的 PyCodeObject。而 test 對應的 PyCodeObject 是一個常量。

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;
}

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();
    }

    //當前函式對應的 PyFunctionObject 入棧
    PUSH((PyObject *)func);
    DISPATCH();
}

建立一個 PyFunctionObject 物件,其 globals, 都來自外部的 PyFrameObject

static PyObject *
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);
    //nargs 引數個數
    Py_ssize_t nargs = oparg - nkwargs;
    PyObject **stack;

    /* Always dispatch PyCFunction first, because these are
       presumed to be the most frequent callable object.
    */
    //C 函式
    if (PyCFunction_Check(func)) {
        PyThreadState *tstate = PyThreadState_GET();

        PCALL(PCALL_CFUNCTION);

        stack = (*pp_stack) - nargs - nkwargs;
        C_TRACE(x, _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames));
    }
    else {
        if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
            /* 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_SETREF(*pfunc, self);
            nargs++;
        }
        else {
            Py_INCREF(func);
        }

        stack = (*pp_stack) - nargs - nkwargs;

        if (PyFunction_Check(func)) {
            x = fast_function(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.  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;
}
TARGET(CALL_FUNCTION) {
    PyObject **sp, *res;
    PCALL(PCALL_ALL);
    sp = stack_pointer;
    res = call_function(&sp, oparg, NULL);
    stack_pointer = sp;
    PUSH(res);
    if (res == NULL) {
        goto error;
    }
    DISPATCH();
}

//Object/methodobject.c
PyObject *
_PyCFunction_FastCallKeywords(PyObject *func, PyObject **stack,
                              Py_ssize_t nargs, PyObject *kwnames)
{
    PyObject *kwdict, *result;
    Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);

    assert(PyCFunction_Check(func));
    assert(nargs >= 0);
    assert(kwnames == NULL || PyTuple_CheckExact(kwnames));
    assert((nargs == 0 && nkwargs == 0) || stack != NULL);
    /* kwnames must only contains str strings, no subclass, and all keys must
       be unique */

    if (nkwargs > 0) {
        kwdict = _PyStack_AsDict(stack + nargs, kwnames);
        if (kwdict == NULL) {
            return NULL;
        }
    }
    else {
        kwdict = NULL;
    }

    result = _PyCFunction_FastCallDict(func, stack, nargs, kwdict);
    Py_XDECREF(kwdict);
    return result;
}

#define PyCFunction_GET_FUNCTION(func) \
        (((PyCFunctionObject *)func) -> m_ml -> ml_meth)

PyObject *
_PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs,
                          PyObject *kwargs)
{
    PyCFunctionObject *func = (PyCFunctionObject*)func_obj;
    PyCFunction meth = PyCFunction_GET_FUNCTION(func);
    PyObject *self = PyCFunction_GET_SELF(func);
    PyObject *result;
    int flags;

    assert(PyCFunction_Check(func));
    assert(func != NULL);
    assert(nargs >= 0);
    assert(nargs == 0 || args != NULL);
    assert(kwargs == NULL || PyDict_Check(kwargs));

    /* _PyCFunction_FastCallDict() must not be called with an exception set,
       because it may clear it (directly or indirectly) and so the
       caller loses its exception */
    assert(!PyErr_Occurred());

    flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST);

    switch (flags)
    {
    case METH_NOARGS:
        if (kwargs != NULL && PyDict_Size(kwargs) != 0) {
            PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
                         func->m_ml->ml_name);
            return NULL;
        }

        if (nargs != 0) {
            PyErr_Format(PyExc_TypeError,
                "%.200s() takes no arguments (%zd given)",
                func->m_ml->ml_name, nargs);
            return NULL;
        }

        result = (*meth) (self, NULL);
        break;

    case METH_O:
        if (kwargs != NULL && PyDict_Size(kwargs) != 0) {
            PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
                         func->m_ml->ml_name);
            return NULL;
        }

        if (nargs != 1) {
            PyErr_Format(PyExc_TypeError,
                "%.200s() takes exactly one argument (%zd given)",
                func->m_ml->ml_name, nargs);
            return NULL;
        }

        result = (*meth) (self, args[0]);
        break;

    case METH_VARARGS:
    case METH_VARARGS | METH_KEYWORDS:
    {
        /* Slow-path: create a temporary tuple */
        PyObject *tuple;

        if (!(flags & METH_KEYWORDS) && kwargs != NULL && PyDict_Size(kwargs) != 0) {
            PyErr_Format(PyExc_TypeError,
                         "%.200s() takes no keyword arguments",
                         func->m_ml->ml_name);
            return NULL;
        }

        tuple = _PyStack_AsTuple(args, nargs);
        if (tuple == NULL) {
            return NULL;
        }

        if (flags & METH_KEYWORDS) {
            result = (*(PyCFunctionWithKeywords)meth) (self, tuple, kwargs);
        }
        else {
            result = (*meth) (self, tuple);
        }
        Py_DECREF(tuple);
        break;
    }

    case METH_FASTCALL:
    {
        PyObject **stack;
        PyObject *kwnames;
        _PyCFunctionFast fastmeth = (_PyCFunctionFast)meth;

        if (_PyStack_UnpackDict(args, nargs, kwargs, &stack, &kwnames) < 0) {
            return NULL;
        }

        result = (*fastmeth) (self, stack, nargs, kwnames);
        if (stack != args) {
            PyMem_Free(stack);
        }
        Py_XDECREF(kwnames);
        break;
    }

    default:
        PyErr_SetString(PyExc_SystemError,
                        "Bad call flags in PyCFunction_Call. "
                        "METH_OLDARGS is no longer supported!");
        return NULL;
    }

    result = _Py_CheckFunctionResult(func_obj, result, NULL);

    return result;
}

PyObject *
PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
    PyThreadState *tstate = PyThreadState_GET();
    return tstate->interp->eval_frame(f, throwflag);
}

static PyObject*
_PyFunction_FastCall(PyCodeObject *co, PyObject **args, Py_ssize_t nargs,
                     PyObject *globals)
{
    PyFrameObject *f;
    PyThreadState *tstate = PyThreadState_GET();
    PyObject **fastlocals;
    Py_ssize_t i;
    PyObject *result;

    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;

    //將引數依次賦值給 f->f_localsplus
    for (i = 0; i < nargs; i++) {
        Py_INCREF(*args);
        fastlocals[i] = *args++;
    }
    result = PyEval_EvalFrameEx(f,0);

    ++tstate->recursion_depth;
    Py_DECREF(f);
    --tstate->recursion_depth;

    return result;
}

#define PyFunction_GET_GLOBALS(func) \
    (((PyFunctionObject *)func) -> func_globals)

#define PyFunction_GET_DEFAULTS(func) \
    (((PyFunctionObject *)func) -> func_defaults)

#define PyFunction_GET_KW_DEFAULTS(func) \
    (((PyFunctionObject *)func) -> func_kwdefaults)
#define PyFunction_GET_CLOSURE(func) \
    (((PyFunctionObject *)func) -> func_closure)
#define PyFunction_GET_ANNOTATIONS(func) \
    (((PyFunctionObject *)func) -> func_annotations)

static PyObject *
fast_function(PyObject *func, PyObject **stack,
              Py_ssize_t nargs, PyObject *kwnames)
{
    PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
    PyObject *globals = PyFunction_GET_GLOBALS(func);
    PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
    PyObject *kwdefs, *closure, *name, *qualname;
    PyObject **d;
    //kwnames 為擴充套件的位置引數
    Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
    Py_ssize_t nd;

    assert(PyFunction_Check(func));
    assert(nargs >= 0);
    assert(kwnames == NULL || PyTuple_CheckExact(kwnames));
    assert((nargs == 0 && nkwargs == 0) || stack != NULL);
    /* kwnames must only contains str strings, no subclass, and all keys must
       be unique */

    PCALL(PCALL_FUNCTION);
    PCALL(PCALL_FAST_FUNCTION);

    if (co->co_kwonlyargcount == 0 && nkwargs == 0 &&
        co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE))
    {   //函式不含有擴充套件引數的情況
        if (argdefs == NULL && co->co_argcount == nargs) {
            return _PyFunction_FastCall(co, stack, nargs, globals);
        }
        //全都是預設引數
        else if (nargs == 0 && argdefs != NULL
                 && co->co_argcount == Py_SIZE(argdefs)) {
            /* function called with no arguments, but all parameters have
               a default value: use default values as arguments .*/
            stack = &PyTuple_GET_ITEM(argdefs, 0);
            return _PyFunction_FastCall(co, stack, Py_SIZE(argdefs), globals);
        }
    }

    kwdefs = PyFunction_GET_KW_DEFAULTS(func);
    closure = PyFunction_GET_CLOSURE(func);
    name = ((PyFunctionObject *)func) -> func_name;
    qualname = ((PyFunctionObject *)func) -> func_qualname;

    if (argdefs != NULL) {
        d = &PyTuple_GET_ITEM(argdefs, 0);
        nd = Py_SIZE(argdefs);
    }
    else {
        d = NULL;
        nd = 0;
    }
    return _PyEval_EvalCodeWithName((PyObject*)co, globals, (PyObject *)NULL,
                                    stack, nargs,
                                    nkwargs ? &PyTuple_GET_ITEM(kwnames, 0) : NULL,
                                    stack + nargs,
                                    nkwargs, 1,
                                    d, (int)nd, kwdefs,
                                    closure, name, qualname);
}

#define GETLOCAL(i)     (fastlocals[i])

#define SETLOCAL(i, value)      do { PyObject *tmp = GETLOCAL(i); \
                                     GETLOCAL(i) = value; \
                                     Py_XDECREF(tmp); } while (0)

static PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
           PyObject **args, Py_ssize_t argcount,
           PyObject **kwnames, PyObject **kwargs,
           Py_ssize_t kwcount, int kwstep,
           PyObject **defs, Py_ssize_t defcount,
           PyObject *kwdefs, PyObject *closure,
           PyObject *name, PyObject *qualname)
{
    PyCodeObject* co = (PyCodeObject*)_co;
    PyFrameObject *f;
    PyObject *retval = NULL;
    PyObject **fastlocals, **freevars;
    PyThreadState *tstate;
    PyObject *x, *u;
    const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount;
    Py_ssize_t i, n;
    PyObject *kwdict;

    if (globals == NULL) {
        PyErr_SetString(PyExc_SystemError,
                        "PyEval_EvalCodeEx: NULL globals");
        return NULL;
    }

    /* Create the frame */
    tstate = PyThreadState_GET();
    assert(tstate != NULL);
    f = PyFrame_New(tstate, co, globals, locals);
    if (f == NULL) {
        return NULL;
    }
    fastlocals = f->f_localsplus;
    freevars = f->f_localsplus + co->co_nlocals;

    /* Create a dictionary for keyword parameters (**kwags) */
    //擴充套件鍵值對引數
    if (co->co_flags & CO_VARKEYWORDS) {
        kwdict = PyDict_New();
        if (kwdict == NULL)
            goto fail;
        i = total_args;
        if (co->co_flags & CO_VARARGS) {
            i++;
        }
        SETLOCAL(i, kwdict);
    }
    else {
        kwdict = NULL;
    }

    /* Copy positional arguments into local variables */
    //擴充套件位置引數
    if (argcount > co->co_argcount) {
        n = co->co_argcount;
    }
    else {
        n = argcount;
    }
    for (i = 0; i < n; i++) {
        x = args[i];
        Py_INCREF(x);
        SETLOCAL(i, x);
    }

    /* Pack other positional arguments into the *args argument */
    if (co->co_flags & CO_VARARGS) {
        u = PyTuple_New(argcount - n);
        if (u == NULL) {
            goto fail;
        }
        SETLOCAL(total_args, u);
        for (i = n; i < argcount; i++) {
            x = args[i];
            Py_INCREF(x);
            PyTuple_SET_ITEM(u, i-n, x);
        }
    }

    /* Handle keyword arguments passed as two strided arrays */
    kwcount *= kwstep;
    for (i = 0; i < kwcount; i += kwstep) {
        PyObject **co_varnames;
        PyObject *keyword = kwnames[i];
        PyObject *value = kwargs[i];
        Py_ssize_t j;

        if (keyword == NULL || !PyUnicode_Check(keyword)) {
            PyErr_Format(PyExc_TypeError,
                         "%U() keywords must be strings",
                         co->co_name);
            goto fail;
        }

        /* Speed hack: do raw pointer compares. As names are
           normally interned this should almost always hit. */
        co_varnames = ((PyTupleObject *)(co->co_varnames))->ob_item;
        for (j = 0; j < total_args; j++) {
            PyObject *name = co_varnames[j];
            if (name == keyword) {
                goto kw_found;
            }
        }

        /* Slow fallback, just in case */
        for (j = 0; j < total_args; j++) {
            PyObject *name = co_varnames[j];
            int cmp = PyObject_RichCompareBool( keyword, name, Py_EQ);
            if (cmp > 0) {
                goto kw_found;
            }
            else if (cmp < 0) {
                goto fail;
            }
        }

        if (j >= total_args && kwdict == NULL) {
            PyErr_Format(PyExc_TypeError,
                         "%U() got an unexpected keyword argument '%S'",
                         co->co_name, keyword);
            goto fail;
        }

        if (PyDict_SetItem(kwdict, keyword, value) == -1) {
            goto fail;
        }
        continue;

      kw_found:
        if (GETLOCAL(j) != NULL) {
            PyErr_Format(PyExc_TypeError,
                         "%U() got multiple values for argument '%S'",
                         co->co_name, keyword);
            goto fail;
        }
        Py_INCREF(value);
        SETLOCAL(j, value);
    }

    /* Check the number of positional arguments */
    if (argcount > co->co_argcount && !(co->co_flags & CO_VARARGS)) {
        too_many_positional(co, argcount, defcount, fastlocals);
        goto fail;
    }

    /* Add missing positional arguments (copy default values from defs) */
    if (argcount < co->co_argcount) {
        Py_ssize_t m = co->co_argcount - defcount;
        Py_ssize_t missing = 0;
        for (i = argcount; i < m; i++) {
            if (GETLOCAL(i) == NULL) {
                missing++;
            }
        }
        if (missing) {
            missing_arguments(co, missing, defcount, fastlocals);
            goto fail;
        }
        if (n > m)
            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);
            }
        }
    }

    /* Add missing keyword arguments (copy default values from kwdefs) */
    if (co->co_kwonlyargcount > 0) {
        Py_ssize_t missing = 0;
        for (i = co->co_argcount; i < total_args; i++) {
            PyObject *name;
            if (GETLOCAL(i) != NULL)
                continue;
            name = PyTuple_GET_ITEM(co->co_varnames, i);
            if (kwdefs != NULL) {
                PyObject *def = PyDict_GetItem(kwdefs, name);
                if (def) {
                    Py_INCREF(def);
                    SETLOCAL(i, def);
                    continue;
                }
            }
            missing++;
        }
        if (missing) {
            missing_arguments(co, missing, -1, fastlocals);
            goto fail;
        }
    }

    /* Allocate and initialize storage for cell vars, and copy free
       vars into frame. */
    for (i = 0; i < PyTuple_GET_SIZE(co->co_cellvars); ++i) {
        PyObject *c;
        int arg;
        /* Possibly account for the cell variable being an argument. */
        if (co->co_cell2arg != NULL &&
            (arg = co->co_cell2arg[i]) != CO_CELL_NOT_AN_ARG) {
            c = PyCell_New(GETLOCAL(arg));
            /* Clear the local copy. */
            SETLOCAL(arg, NULL);
        }
        else {
            c = PyCell_New(NULL);
        }
        if (c == NULL)
            goto fail;
        //cellvars 在 locals 變數之後
        SETLOCAL(co->co_nlocals + i, c);
    }

    /* Copy closure variables to free variables */
    for (i = 0; i < PyTuple_GET_SIZE(co->co_freevars); ++i) {
        PyObject *o = PyTuple_GET_ITEM(closure, i);
        Py_INCREF(o);
        freevars[PyTuple_GET_SIZE(co->co_cellvars) + i] = o;
    }

    /* Handle generator/coroutine/asynchronous generator */
    if (co->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) {
        PyObject *gen;
        PyObject *coro_wrapper = tstate->coroutine_wrapper;
        int is_coro = co->co_flags & CO_COROUTINE;

        if (is_coro && tstate->in_coroutine_wrapper) {
            assert(coro_wrapper != NULL);
            PyErr_Format(PyExc_RuntimeError,
                         "coroutine wrapper %.200R attempted "
                         "to recursively wrap %.200R",
                         coro_wrapper,
                         co);
            goto fail;
        }

        /* Don't need to keep the reference to f_back, it will be set
         * when the generator is resumed. */
        Py_CLEAR(f->f_back);

        PCALL(PCALL_GENERATOR);

        /* Create a new generator that owns the ready to run frame
         * and return that as the value. */
        if (is_coro) {
            gen = PyCoro_New(f, name, qualname);
        } else if (co->co_flags & CO_ASYNC_GENERATOR) {
            gen = PyAsyncGen_New(f, name, qualname);
        } else {
            gen = PyGen_NewWithQualName(f, name, qualname);
        }
        if (gen == NULL)
            return NULL;

        if (is_coro && coro_wrapper != NULL) {
            PyObject *wrapped;
            tstate->in_coroutine_wrapper = 1;
            wrapped = PyObject_CallFunction(coro_wrapper, "N", gen);
            tstate->in_coroutine_wrapper = 0;
            return wrapped;
        }

        return gen;
    }

    retval = PyEval_EvalFrameEx(f,0);

fail: /* Jump here from prelude on failure */

    /* decref'ing the frame can cause __del__ methods to get invoked,
       which can call back into Python.  While we're done with the
       current Python frame (f), the associated C stack is still in use,
       so recursion_depth must be boosted for the duration.
    */
    assert(tstate != NULL);
    ++tstate->recursion_depth;
    Py_DECREF(f);
    --tstate->recursion_depth;
    return retval;
}

PyFrameObject *
PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
            PyObject *locals)
{
    PyFrameObject *back = tstate->frame;
    PyFrameObject *f;
    PyObject *builtins;
    Py_ssize_t i;

    if (back == NULL || back->f_globals != globals) {
        builtins = _PyDict_GetItemId(globals, &PyId___builtins__);
        if (builtins) {
            if (PyModule_Check(builtins)) {
                builtins = PyModule_GetDict(builtins);
                assert(builtins != NULL);
            }
        }
        if (builtins == NULL) {
            /* No builtins!              Make up a minimal one
               Give them 'None', at least. */
            builtins = PyDict_New();
            if (builtins == NULL ||
                PyDict_SetItemString(
                    builtins, "None", Py_None) < 0)
                return NULL;
        }
        else
            Py_INCREF(builtins);

    }
    else {
        /* If we share the globals, we share the builtins.
           Save a lookup and a call. */
        builtins = back->f_builtins;
        assert(builtins != NULL);
        Py_INCREF(builtins);
    }
    if (code->co_zombieframe != NULL) {
        f = code->co_zombieframe;
        code->co_zombieframe = NULL;
        _Py_NewReference((PyObject *)f);
        assert(f->f_code == code);
    }
    else {
        Py_ssize_t extras, ncells, nfrees;
        ncells = PyTuple_GET_SIZE(code->co_cellvars);
        nfrees = PyTuple_GET_SIZE(code->co_freevars);
        //程式碼段的各個變數要在新建的 PyFrameObject 中保留
        extras = code->co_stacksize + code->co_nlocals + ncells +
            nfrees;
        if (free_list == NULL) {
            f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type,
            extras);
            if (f == NULL) {
                Py_DECREF(builtins);
                return NULL;
            }
        }
        else {
            assert(numfree > 0);
            --numfree;
            f = free_list;
            free_list = free_list->f_back;
            if (Py_SIZE(f) < extras) {
                PyFrameObject *new_f = PyObject_GC_Resize(PyFrameObject, f, extras);
                if (new_f == NULL) {
                    PyObject_GC_Del(f);
                    Py_DECREF(builtins);
                    return NULL;
                }
                f = new_f;
            }
            _Py_NewReference((PyObject *)f);
        }

        f->f_code = code;
        //將呼叫者的 PyCodeObject 的 co_nlocals n_cells, nfrees 賦值給 f_localsplus
        extras = code->co_nlocals + ncells + nfrees; //沒有包含  code->stacksize
        f->f_valuestack = f->f_localsplus + extras;
        for (i=0; i<extras; i++)
            f->f_localsplus[i] = NULL;
        f->f_locals = NULL;
        f->f_trace = NULL;
        f->f_exc_type = f->f_exc_value = f->f_exc_traceback = NULL;
    }
    f->f_stacktop = f->f_valuestack;
    f->f_builtins = builtins;
    Py_XINCREF(back);
    f->f_back = back;
    Py_INCREF(code);
    Py_INCREF(globals);
    f->f_globals = globals;
    /* Most functions have CO_NEWLOCALS and CO_OPTIMIZED set. */
    if ((code->co_flags & (CO_NEWLOCALS | CO_OPTIMIZED)) ==
        (CO_NEWLOCALS | CO_OPTIMIZED))
        ; /* f_locals = NULL; will be set by PyFrame_FastToLocals() */
    else if (code->co_flags & CO_NEWLOCALS) {
        locals = PyDict_New();
        if (locals == NULL) {
            Py_DECREF(f);
            return NULL;
        }
        f->f_locals = locals;
    }
    else {
    //用全域性變數初始化區域性變數
        if (locals == NULL)
            locals = globals;
        Py_INCREF(locals);
        f->f_locals = locals;
    }

    f->f_lasti = -1;
    f->f_lineno = code->co_firstlineno;
    f->f_iblock = 0;
    f->f_executing = 0;
    f->f_gen = NULL;

    _PyObject_GC_TRACK(f);
    return f;
}

對於 C 函式,呼叫 (func->m_ml -> ml_meth)(args) 引數,這裡引數包括沒有引數,
一個引數,可變引數

對於 Python 本身的函式呼叫 PyEval_EvalFrameEx,對應無引數的進行了優化,有引數的
進行各種解析。

至此,我們應該對 Python 的執行機制有了更加深刻的理解。大體描述如下:
編譯的時候,每個函式會編譯為一個單獨的 PyCodeObject;
執行的是時候首先,整個檔案是一個 PyFrameObject 物件,每個 PyFrameObject 物件包含
PyCodeObject,在 PyCodeObject 執行過程中遇到函式呼叫,會建立一個新的 PyFunctionObject 物件,
並將編譯的 PyCodeObject 賦值給該物件,根據函式型別判斷是 C 函式還是 Python
本身的函式。如果是 C 函式, 對呼叫 (func->m_ml -> ml_meth)(args),如果是 Python
函式,會建立一個新的 PyFrameObject,並用 PyFunctionObject 初始化該 PyFrameObject
物件,最後執行 PyFunctionObject 中 PyCodeObject 中的位元組碼。

此外,還有一個細節需要注意的是 f_globals,雖然所有函式共享全域性變數,但是,
事實上每個函式訪問全域性變數的時候,並不是依次向 上搜索,而是在呼叫的是,
直接將父函式的 globals 傳遞給子函式。這樣子函式直接搜尋自身的 f_globals
就可以了。這樣雖然佔用了記憶體,但是提高了效率。

兩個問題:
1. 遞迴是怎麼實現的?
2. 前面定義的函式可以呼叫後面定義的函式麼? 為什麼?

引數

python 中引數包括

  1. 位置引數,如 f(a, b)
  2. 鍵值對引數,如 f(a = 1, b = 2)
  3. 擴充套件位置引數,如 f(a, b, *argv)
  4. 擴充套件鍵值對引數,如 f(a, b, **argv)

例如:

#!/usr/bin/env python
# encoding: utf-8

def test():
    pass

def test1(a):
    pass

def test2(a, b):
    pass

def test3(a, b, *argv):
    pass

def test4(a, b, **argv):
    pass

test()
test1(1)
test2(1, 2)
test3(1, 2, 3)
test3(1, 2, 3, 4)
test4(1, 2, c = 3, d = 4)

編譯之後

magic b'330d0d0a'
moddate b'8788345b' (Thu Jun 28 15:04:39 2018)
files sz 803
code
   argcount 0
   nlocals 0
   stacksize 6
   flags 0040
   code
      b'6400640184005a006402640384005a016404640584005a02640664078400'
      b'5a036408640984005a046500830001006501640a830101006502640a640b'
      b'830201006503640a640b640c830301006503640a640b640c640d83040100'
      b'6504640a640b640c640d640e8d040100640f5300'
  4           0 LOAD_CONST               0 (<code object test at 0x103828ae0, file "/tmp/a.py", line 4>)
              2 LOAD_CONST               1 ('test')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (test)

  7           8 LOAD_CONST               2 (<code object test1 at 0x103839ae0, file "/tmp/a.py", line 7>)
             10 LOAD_CONST               3 ('test1')
             12 MAKE_FUNCTION            0
             14 STORE_NAME               1 (test1)

 10          16 LOAD_CONST               4 (<code object test2 at 0x103839f60, file "/tmp/a.py", line 10>)
             18 LOAD_CONST               5 ('test2')
             20 MAKE_FUNCTION            0
             22 STORE_NAME               2 (test2)

 13          24 LOAD_CONST               6 (<code object test3 at 0x103837b70, file "/tmp/a.py", line 13>)
             26 LOAD_CONST               7 ('test3')
             28 MAKE_FUNCTION            0
             30 STORE_NAME               3 (test3)

 16          32 LOAD_CONST               8 (<code object test4 at 0x1038375d0, file "/tmp/a.py", line 16>)
             34 LOAD_CONST               9 ('test4')
             36 MAKE_FUNCTION            0
             38 STORE_NAME               4 (test4)

 19          40 LOAD_NAME                0 (test)
             42 CALL_FUNCTION            0               //沒引數,直接呼叫
             44 POP_TOP

 20          46 LOAD_NAME                1 (test1)
             48 LOAD_CONST              10 (1)
             50 CALL_FUNCTION            1               //一個引數
             52 POP_TOP

 21          54 LOAD_NAME                2 (test2)
             56 LOAD_CONST              10 (1)
             58 LOAD_CONST              11 (2)
             60 CALL_FUNCTION            2               //兩個引數
             62 POP_TOP

 22          64 LOAD_NAME                3 (test3)
             66 LOAD_CONST              10 (1)
             68 LOAD_CONST              11 (2)
             70 LOAD_CONST              12 (3)
             72 CALL_FUNCTION            3               //三個引數
             74 POP_TOP

 23          76 LOAD_NAME                3 (test3)
             78 LOAD_CONST              10 (1)
             80 LOAD_CONST              11 (2)
             82 LOAD_CONST              12 (3)
             84 LOAD_CONST              13 (4)          //四個引數
             86 CALL_FUNCTION            4
             88 POP_TOP

 24          90 LOAD_NAME                4 (test4)
             92 LOAD_CONST              10 (1)
             94 LOAD_CONST              11 (2)
             96 LOAD_CONST              12 (3)
             98 LOAD_CONST              13 (4)
            100 LOAD_CONST              14 (('c', 'd')) //鍵值對
            102 CALL_FUNCTION_KW         4              //四個引數
            104 POP_TOP
            106 LOAD_CONST              15 (None)
            108 RETURN_VALUE
   consts
      code
         argcount 0
         nlocals 0                      //沒有引數,沒有區域性變數
         stacksize 1
         flags 0043
         code b'64005300'
  5           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE
         consts
            None
         names ()
         varnames ()
         freevars ()
         cellvars ()
         filename 
            
           

相關推薦

Python 原始碼分析函式機制

在 python 中函式也是一個物件 typedef struct { PyObject_HEAD PyObject *func_code; /* 函式編譯之後的 PyCodeObject, the __code__ attribute */ PyOb

Python 原始碼分析位元組碼基本操作

本文基於 Python 3.6.4 編譯器生成位元組碼,你可通過如下程式碼片段得到 python 原始碼對應的位元組碼 #!/usr/bin/env python # encoding: utf-8 import sys import dis filename=sys.argv

Python 原始碼分析執行時環境

python 執行時環境 執行環境是一個全域性的概念,而執行環境就是指棧幀 當執行時環境已經準備好的時候,執行第一行程式碼的函式就是 PyEval_EvalFrame 函式 PyObject * PyEval_EvalFrame(PyFrameObject *f) {

python 原始碼分析型別系統

型別系統 一般物件是不能靜態分配的,而 python 所有內建物件都是靜態分配的 typedef struct _object { _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; struct _typeobject

Python 原始碼分析初體驗

在 python 中,物件就是 C 中結構體在堆上申請的一塊記憶體,一般來說, 物件不能靜態初始化,並且也不能在棧上空間生存。唯一的例外就是型別 物件,python 中所有的內建的型別物件(整數型別物件,字元型別物件) 都是靜態初始化的 python 架構 外部呼叫

Android原始碼分析訊息機制——Handler原始碼解析

 Android的訊息機制主要是指Handler的執行機制,Handler是Android訊息機制上層介面的實現,它的執行需要Message、MessageQueue和Looper的支撐,下面就來分別介紹它們的實現原理。 1、Message原始碼解析  首先來了解一下Messag

Memcached原始碼分析儲存機制Slabs(7)

文章列表: 《Memcached原始碼分析 - Memcached原始碼分析之總結篇(8)》前言 這一章我們重點講解Memcached的儲存機制Slabs。Memcached儲存Item的程式碼都是在slabs.c中來實現的。 在解讀這一章前,我們必須先了

JVM原始碼分析Attach機制實現完全解讀

Attach是什麼 在講這個之前,我們先來點大家都知道的東西,當我們感覺執行緒一直卡在某個地方,想知道卡在哪裡,首先想到的是進行執行緒dump,而常用的命令是jstack ,我們就可以看到如下執行緒棧了 2014-06-18 12:56:14 Full thread dump Java HotSpot(

Android事件分發機制原始碼分析Activity篇

在之前的事件分發分析中,曾提及到View的事件是由ViewGroup分發的,然而ViewGroup的事件我們只是稍微帶過是由Activity分發的。而我們知道,事件產生於使用者按下螢幕的一瞬間,事件生成後,經過一系列的過程來到我們的Activity層,那麼事件是怎樣從Activity傳遞

ipset原始碼分析kadt和uadt回撥函式

一定要清楚自己在幹什麼,每行程式碼在幹什麼,這樣寫的程式碼才能做到心中有數。 之前看到ip_set_hash_ip.chash_ip4_uadt和hash_ip4_kadt函式,就一直很好奇這兩個函式是幹什麼呢? 下面我來帶你一步步剖析這兩個函式 一、kadt和uadt回撥函式的註冊

Hadoop原始碼分析二(RPC機制Call處理)

下面介紹在整個處理機制中怎麼把具體的Request Call轉換並呼叫到整體的實現邏輯。 主要以NameNode Client PRC Server作為例子來說明,整個轉換通過Google Protocol Buffer RPC來實現。           fina

STL原始碼分析vector(二)—核心函式 push_back及insert_aux

說明: STL原始碼分析系列部落格的使用的是https://www.sgi.com/tech/stl/download.html 裡面的STL v2.03版.不同的STL或許會有所不同。 其它vector內容請參照本系列其它部落格。 主要函式分析

Android Wi-Fi原始碼分析WifiService操作Wi-Fi(一):分析Wifi.c中的wifi_load_driver()函式

Wi-Fi原始碼分析之WifiService操作Wi-Fi(一) 分析Wifi.c中的wifi_load_driver()函式 int wifi_load_driver() { AL

(三)ghostscript原始碼分析interp函式

interp是核心函式,理解此函式要下極大的功夫。 對有些及難懂的註釋了一些。 交流分享是一種學習的好方法。 /* Main interpreter. */ /* If execution terminates normally, return e_InterpreterExit. */

(二)ghostscript原始碼分析interp()函式IREF_NEXT巨集分析

interp()函式用了大量的巨集。IREF_NEXT只是其中一個巨集,但是出現的頻率很高。 但是透徹的理解這個巨集將為理解interp()函式提供便利。 它的定義形式如下: #define IREF_NEXT(ip)/ ((const ref_packed *)((const re

(一)ghostscript原始碼分析interp()函式的第二個引數

/* Main interpreter. */ /* If execution terminates normally, return e_InterpreterExit. */ /* If an error occurs, leave the current object in *perror_o

ghostscript原始碼分析 scan_token()函式 (詞法分析器iscan.c)

scan_token()函式很重要,ghostscript寫得比較瑣碎難懂,裡面有些有英文解釋。 我只對我關注的部分加了些中文註釋。當然不是所有的都理解了。但是功能還是清楚了的,像某些函式介面。 如果讓我寫的話,我一定比他寫得更清晰。哈哈,當然他的scan_token基本的框架 設計還是

spark原始碼分析Master原始碼主備切換機制分析

Master原始碼分析之主備切換機制 1.當選為leader之後的操作 //ElectedLeader 當選leader case ElectedLeader => {

Android Wi-Fi原始碼分析wpa_supplicant初始化(四):wpa_supplicant_init_iface函式分析

wpa_config_read分析 路徑為:external\wpa_supplicant_8\wpa_supplicant\config_file.c struct wpa_config * wpa_config_read(const char *na

python效能優化函式執行時間分析

最近發現專案API請求比較慢,通過抓包發現主要是response時間太長,於是就開始進行優化工作。優化工作的關鍵一步是定位出問題的瓶頸,對於優化速度來說,從優化函式執行時間這個維度去切入是一個不錯的選擇。 本文側重分析,不展開如何優化 利器 工欲善其事,必先利其器,我們需要一套方便高效的工具記