• Python源码——虚拟机函数机制


    Python虚拟机中的函数机制

    x86平台上函数调用发生时,系统会在运行时栈中创建新的栈帧,用于函数的执行
    在Python中,PyFrameObject对象是对栈帧的模拟,Python虚拟机在执行函数调用时会动态地创建新的PyFrameObject对象。随着函数调用链的
    增长,这些PyFrameObject对象之间也会连接成一条PyFrameObject对象链。

    1.PyFunctionObject对象
    在Python中,任何东西都是一个对象,函数也不例外
    函数是通过PyFunctionObject来实现的

    typedef struct {
    PyObject_HEAD
    PyObject *func_code; /* 对应函数编译后的PyCodeObject对象*/
    PyObject *func_globals; /* 函数运行时的global名字空间爱你*/
    PyObject *func_defaults; /* 默认参数(tuple或NULL)*/
    PyObject *func_closure; /* Null or a tuple of cell objects,用于实现closure*/
    PyObject *func_doc; /* 函数的文档(PyStringObject)*/
    PyObject *func_name; /* 函数名称,函数的__name__属性,(PyStringObject)*/
    PyObject *func_dict; /* 函数的__dict__属性(PyDictObject或NULL) */
    PyObject *func_weakreflist; /* List of weak references */
    PyObject *func_module; /* 函数的__module__,可以是任何对象 */

    /* 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和PyFunctionObject。
    PyCodeObject是编译时的结果
    PyCodeObject对象是对一段Python源代码的静态表示。源代码编译后,一个Code Block会产生一个且只有一个PyCodeObject
    这个PyCodeObject对象中包含了这个Code Block的一些静态的信息,所谓静态的信息是指可以从源代码中看到的信息。这些
    信息会分别存储在PyCodeObject的常量表co_consts,符号表co_names以及字节码序列co_code中,PyCodeObject是编译时的结果


    PyFunctionObject对象是Python代码在运行时动态产生的,是在执行一个def语句的时候创建的。
    在PyFunctionObject中,包含的函数静态信息,存储在func_code中,func_code指向与函数代码对应的PyCodeObject对象。PyFunctionObject
    对象中包含了一些函数执行时必须的动态信息,即上下文信息。

    PyCodeObject------func_code-----PyFunctionObject


    2.无参函数调用
    2.1 函数对象的创建

    func_0.py
    def f():
    print "Function"
    f()
    >>> co = compile(open('func_0.py').read(),'func_0.py','exec')

    >>> type(co)
    <type 'code'>
    >>> type(co.co_consts[0])
    <type 'code'>
    >>> co.co_name
    '<module>'
    >>> co.co_consts[0].co_name
    'f'

    >>> import dis
    >>> import marsha1
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    ImportError: No module named marsha1
    >>> import marshal
    >>> f = open('func_0.pyc','rb')
    >>> magic=f.read(4)
    >>> mtime=f.read(4)
    >>>
    >>> code=marshal.load(f)
    >>> dis.dis(code)
    1 0 LOAD_CONST 0 (<code object f at 00AF6728, file "C:
    \Documents and Settings\happy\func_0.py", line 1>)
    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
    >>>

    Python实现时,把函数的声明与函数的实现分离
    在调用一个函数之前,Python必须首先创建这个函数对象。函数对象的创建工作正式在def f()代码处完成的。
    def是函数的声明语句,从Python虚拟机的角度看,它其实是函数对象的创建语句。

    def-->MAKE_FUNCTION
    case MAKE_FUNCTION:
    v = POP(); /* 获得与函数f对应的PyCodeObject对象,在MAKE_FUNCTION之前,Python虚拟机会执行LOAD_CONST 0*/
    x = PyFunction_New(v, f->f_globals); //v是PyCodeObject对象,而f_globals对象则是当前PyFrameObject对象中维护的global名字空间
    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); //新建的PyFunctionObject对象通过PUSH操作被压入到运行时栈中,随后的STORE_NAME和LOAD_NAME起作用
    break;



    PyObject *
    PyFunction_New(PyObject *code, PyObject *globals)
    {
    PyFunctionObject *op = PyObject_GC_New(PyFunctionObject,
    &PyFunction_Type); //申请PyFunctionObject对象所需的内存空间
    static PyObject *__name__ = 0;
    if (op != NULL) {
    PyObject *doc;
    PyObject *consts;
    PyObject *module;
    //初始化PyFunctionObject对象中的各个域
    op->func_weakreflist = NULL;
    Py_INCREF(code);
    op->func_code = code; //设置PyCodeObject对象
    Py_INCREF(globals);
    op->func_globals = globals; //设置global名字空间
    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; //函数中的常量对象表
    //函数的文档
    if (PyTuple_Size(consts) >= 1) {
    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;
    }

    2.2 函数调用
    CALL_FUNCTION指令开始,Python虚拟机进入了函数调用动作:

    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;
    }
    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;
    //获得PyFunctionObject对象
    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) {
    int flags = PyCFunction_GET_FLAGS(func);
    PyThreadState *tstate = PyThreadState_GET();

    PCALL(PCALL_CFUNCTION);
    if (flags & (METH_NOARGS | METH_O)) {
    PyCFunction meth = PyCFunction_GET_FUNCTION(func);
    PyObject *self = PyCFunction_GET_SELF(func);
    if (flags & METH_NOARGS && na == 0) {
    C_TRACE(x, (*meth)(self,NULL));
    }
    else if (flags & METH_O && na == 1) {
    PyObject *arg = EXT_POP(*pp_stack);
    C_TRACE(x, (*meth)(self,arg));
    Py_DECREF(arg);
    }
    else {
    err_args(func, flags, na);
    x = NULL;
    }
    }
    else {
    PyObject *callargs;
    callargs = load_args(pp_stack, na);
    READ_TIMESTAMP(*pintr0);
    C_TRACE(x, PyCFunction_Call(func,callargs,NULL));
    READ_TIMESTAMP(*pintr1);
    Py_XDECREF(callargs);
    }
    } else {
    //对PyFunctionObject对象进行调用
    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_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); //n指明了在运行时栈中,栈顶的多少个元素是与参数相关的
    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).
    */
    //pp_stack就是在CALL_FUNCTION的指令代码中传入的当前运行时栈的栈顶指针
    while ((*pp_stack) > pfunc) {
    w = EXT_POP(*pp_stack);
    Py_DECREF(w);
    PCALL(PCALL_POP);
    }
    return x;
    }

    无参函数进入fast_function之后,最终会进入PyEval_EvalFrameEx,Python虚拟机在一个新的PyFrameObject(栈帧)环境中开始一次执行新的字节码
    指令序列的循环,这个新的字节码指令序列正式函数所对应的字节码指令序列。无参函数进入快速通道

    3.函数执行时的名字空间
    在执行LOAD_NAME指令时,Python虚拟机会以此从三个PyDictObject对象中进行搜索,搜索的顺序是:f_locals、f_globals、f_builtins。
    通过globals传递,才使得函数可以使用函数外的符号

    在一开始执行py文件时,它的f_locals和f_globals指向同一个PyDictObject对象

    C语言中函数是否可调用(可编译通过)完全是基于源代码中函数出现的位置做的分析
    Python依靠的是运行时的名字空间


    4.函数参数的实现
    4.1 参数类别
    位置参数(positioanal argument):f(a,b) a、b被称为位置参数
    键参数(key argument):f(a,b,name='Python') name='Python'被称为键参数
    扩展位置参数(excess positional argument):def f(a,b,*list), *list被称为扩展位置参数
    扩展键参数(excess key argument):def f(a,b,**keys),**key被称为扩展键参数

    Python虚拟机开始执行CALL_FUNCTION指令时,会首先获得一个指令参数oparg。oparg,记录着函数参数的个数信息
    CALL_FUNCTION指令参数的长度是两个字节,在低字节,记录着位置参数的个数,在高字节,记录着键参数的个数

    两个与参数有关的信息:co_argcount和co_nlocals
    在Python中,函数参数和函数的局部变量关系非常密切,在某种意义上,函数就是一种函数局部变量,它们在内存中是连续放置的。
    当Python需要为函数申请存放局部变量的内存空间时,就需要通过co_nlocals知道局部变量的总数。通过co_argcount告诉Python虚拟机函数一共有多少个参数


    由于位置参数会导致一条LOAD_CONST指令,而键参数会导致两条LOAD_CONST指令,n=na+2*nk

    5.函数中局部变量的访问
    局部变量和函数参数一样,都在f_localsplus中运行时栈前面的那段内存空间中。

    6.嵌套函数、闭包与decorator

    base = 1
    def get_compare(base):
    def real_compare(value):
    return value > base
    return real_compare

    compare_with_10 = get_compare(10)
    print compare_with_10(5)
    print compare_with_10(20)

    名字空间与函数捆绑后的结果被称为一个闭包(closure)

    这一部分不是很理解,下次再接着看

  • 相关阅读:
    并发通信、生产者消费者模型
    进程和线程的标识,守护模式
    IO多路复用
    网络编程(三):非阻塞套接字
    网络编程(二):套接字Socket
    网络编程(一):基础知识
    python标准库:base64模块
    @classmethod 和 @staticmethod
    Python特性及解释目录(不间断更新)
    Keras 整理及部分源码分析目录(不间断更新)
  • 原文地址:https://www.cnblogs.com/moonflow/p/2368449.html
Copyright © 2020-2023  润新知