zoukankan      html  css  js  c++  java
  • 从函数内函数定义看python的函数实现

    一、问题

    在C/C++中,函数的定义本质上是在编译阶段完成,而函数调用是由链接完成。但是对于python这种语言,函数的定义和调用都是由解释器在运行时完成,或者说,解释器在执行函数定义的时候,同样是生成了虚拟机指令,这个指令通常可以理解为MAKE_FUNCTION这个虚拟机指令,这个指令生成的则是一个PyFunctionObject对象。对应的,在函数调用的时候执行的是CALL_FUNCTION,这个指令需要生成一个PyFrameObject对象。

    二、CALL_FUNCTION的执行

    1、函数的调用

    一个函数的构成。静态代码信息大致对应函数模版,也就是函数本身的描述信息:例如函数的虚拟机指令集合、使用的常量、使用的自由变量数量、局部变量数量等信息。但是,函数执行时候,最为关键的是需要一个栈信息,这个是函数的动态特性。如果一个函数需要在多线程中运行,那么它必须有自己私有的堆栈信息。
    可以看到,在执行CALL_FUNCTION指令时,会动态创建一个PyFrameObject对象,这个变量从名字上看就可以知道它对于保存局部变量有着责无旁贷的义务。
    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)
    {
    ……
    assert(tstate != NULL);
    f = PyFrame_New(tstate, co, globals, locals);
    ……
    }

    2、堆栈的生成

    那么一个函数运行时堆栈中使用的临时变量存储在哪里呢?
    在调用PyObject_GC_NewVar的时候,额外要求在PyFrameObject结构之后分配extras个Object对象,这个extras包含了堆栈大小,局部变量、cell变量和free变量,一个函数的运行时信息就存储在这里,它的基地址由f_localsplus执行。从实现上看,f_localsplus定义为
    PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
    它定义在函数的最后,从而对象创建之后,这个变量就自动指向了额外变量
    PyFrameObject *
    PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
    PyObject *locals)
    {
    ……
    Py_ssize_t extras, ncells, nfrees;
    ncells = PyTuple_GET_SIZE(code->co_cellvars);
    nfrees = PyTuple_GET_SIZE(code->co_freevars);
    extras = code->co_stacksize + code->co_nlocals + ncells +
    nfrees;
    if (free_list == NULL) {
    f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type,
    extras);
    ……
    f->f_code = code;
    extras = code->co_nlocals + ncells + nfrees;
    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;

    ……
    }

    三、一个推论

    既然函数定义也有对应的机器指令,那么如果一个函数中嵌套了另一个函数定义,在执行外层函数的时候,内层函数的定义依然会被执行。并且由于两次执行的栈帧不同,所以每次执行都会由MAKE_FUNCTION生成新的函数对象。我们可以通过下面的代码验证一下,可以看到返回的两个内部函数定义并不相同,虽然逻辑上看是相同的函数。
    tsecer@harry: cat innerdef.py
    def outter():
    def inner():
    print("something")
    return inner

    inner1 =outter()
    inner2 = outter()

    print(inner1, inner2)

    tsecer@harry: python innerdef.py
    (<function inner at 0x7f6e6b5015f0>, <function inner at 0x7f6e6b501668>)
    tsecer@harry:

  • 相关阅读:
    一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](九)
    一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](八)
    一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](七)
    一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](六)
    C#初学者们,请离代码生成器远点!!!
    数据库管理工具神器-DataGrip,可同时管理多个主流数据库[SQL Server,MySQL,Oracle等]连接
    一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](五)
    一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](四)
    一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](三)
    一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](二)
  • 原文地址:https://www.cnblogs.com/tsecer/p/10382793.html
Copyright © 2011-2022 走看看