Python源码分析

本文环境python2.5系列
参考书籍<<Python源码剖析>>

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

分析

typedef struct {PyObject_HEADPyObject *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 (<code object f at 0x1044fd630, file "test1.py", line 1>)3 MAKE_FUNCTION            06 STORE_NAME               0 (f)4           9 LOAD_NAME                0 (f)12 CALL_FUNCTION            015 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 */   //获取codex = 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;                           // 设置函数对应的PyCodeObjectPy_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) {                          // 获取函数的docdoc = PyTuple_GetItem(consts, 0);if (!PyString_Check(doc) && !PyUnicode_Check(doc))doc = Py_None;}elsedoc = 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;}}elsereturn 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_TSCx = call_function(&sp, oparg, &intr0, &intr1);
#elsex = call_function(&sp, oparg);
#endifstack_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组成,所有乘2PyObject **pfunc = (*pp_stack) - n - 1;      // 获取函数对象PyObject *func = *pfunc;PyObject *x, *w;/* Always dispatch PyCFunction first, because these arepresumed 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++;} elsePy_INCREF(func);READ_TIMESTAMP(*pintr0);if (PyFunction_Check(func))                                     // 检查是否是函数类型x = fast_function(func, pp_stack, n, na, nk);               // 处理快速方法elsex = do_call(func, pp_stack, na, nk);READ_TIMESTAMP(*pintr1);Py_DECREF(func);}/* Clear the stack of the function object.  Also removesthe 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 specializedPyFrame_New() that doesn't take locals, but doestake 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;                // 返回调用执行的结果

至此,执行完成结果便会返回。
本次的无参函数的调用就分析完成。

Python源码学习:Python函数浅析-无参函数相关推荐

  1. Python源码学习:Python函数浅析-有参函数

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 继续上一篇无参函数的调用后,本文将分析Python中的有参函数的大致流程,在Pyth ...

  2. Python源码学习:Python函数浅析-函数闭包

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 上一篇分析了函数参数的分析后,本文分析函数闭包的实现.函数闭包即函数定义和函数表达式 ...

  3. Python源码学习:Python类机制分析-用户自定义类

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 上一文,分析了Python在启动初始化时,对内置类的一个基本的初始化流程,本文就简析 ...

  4. Python源码学习:Python类机制分析

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 本文主要分析Python中类时如何实现的,在Python中,一切都是对象:任何对象都 ...

  5. Python源码学习:启动流程简析

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> Python简介: python主要是动态语言,虽然Python语言也有编译,生成中 ...

  6. python源码学习_【Python学习】Python源码阅读(一)

    最近想读读Python源码,任何东西学习方法基本都是一样的,先从总体框架进行了解,再从自己侧重的方面逐步深入. 1. Python总体架构 左边是Python提供的大量的模块.库以及用户自定义的模块. ...

  7. Python 源码学习:类型和对象

    Python 是一门解释型,动态类型,多范式的编程语言,当我们从 python.org 下载并安装运行 Python 的某个分发版本时,我们实际上是在运行由 C 语言编写的 CPython ,除此之外 ...

  8. Python源码学习笔记:Python程序执行过程与字节码

    Python程序执行过程与字节码 注:本篇是根据教程学习记录的笔记,部分内容与教程是相同的,因为转载需要填链接,但是没有,所以填的原创,如果侵权会直接删除. 问题: 我们每天都要编写一些Python程 ...

  9. Python源码学习:多线程实现机制

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 本文分析Python中的多线程机制,主要通过一个多线程的脚本来分析多线程的基本操作与 ...

最新文章

  1. GAD计算机辅助诊断,GAD-2和GAD-7在心血管门诊焦虑筛查中的信度与效度分析
  2. Kubernetes基础学习(一)
  3. Python数据分析笔记——Numpy、Pandas库
  4. Linux系统编程24:基础IO之在Linux下深刻理解C语言中的动静态库以及头文件和库的关系
  5. 手工杀毒之“三十六计”
  6. 使用JavaScript将图片保存至本地
  7. kotlin的属性委托
  8. csv转vcf格式网页工具-快速导入手机通讯录
  9. 数据结构之一元多项式相加
  10. JavaScript|日期格式化、今天、昨天、明天和某天
  11. 华硕 X542UQ REV:2.1
  12. win7电脑变身WiFi热点,让手机、笔记本共享上网
  13. 业务范围(business area)
  14. HTML css jQuery实现导航栏(华丽动画)
  15. 数据结构题集(严书)查找 常见习题代码
  16. 图像采集卡的种类和区别
  17. 微信支付踩坑合集:微信小程序支付失败是什么原因?
  18. SQL总结--存储过程
  19. 在物联网中保持数据合规
  20. 排序算法--快速排序(QuickSort)、 3区快速排序(3 Way QuickSort)原理、适用场景及代码示例

热门文章

  1. GPT-3再进化:通过浏览网页来提高事实准确率
  2. 谷歌编程语言年度榜NO.1:知识体系总结(2021版)
  3. 华为昇腾师资培训沙龙·南京场 |华为昇腾 ACL 语言开发实践全程干货来了!看完就实操系列...
  4. 算法对建筑业的影响,不仅仅是画图
  5. RANet : 分辨率自适应网络效果和性能的best trade-off | CVPR 2020
  6. 节后招人平均工资9000上热搜,为什么有些人去哪里都值钱?
  7. 流行于机器学习竞赛的Boosting,这篇文章讲的非常全了
  8. ICCV 2019 | 无需数据集的Student Networks
  9. 喜得爱女,吴恩达深情撰文:欢迎你来到新世界!
  10. 最新机器学习开源项目Top10