Python源码学习:Python函数浅析-无参函数
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函数浅析-无参函数相关推荐
- Python源码学习:Python函数浅析-有参函数
Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 继续上一篇无参函数的调用后,本文将分析Python中的有参函数的大致流程,在Pyth ...
- Python源码学习:Python函数浅析-函数闭包
Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 上一篇分析了函数参数的分析后,本文分析函数闭包的实现.函数闭包即函数定义和函数表达式 ...
- Python源码学习:Python类机制分析-用户自定义类
Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 上一文,分析了Python在启动初始化时,对内置类的一个基本的初始化流程,本文就简析 ...
- Python源码学习:Python类机制分析
Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 本文主要分析Python中类时如何实现的,在Python中,一切都是对象:任何对象都 ...
- Python源码学习:启动流程简析
Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> Python简介: python主要是动态语言,虽然Python语言也有编译,生成中 ...
- python源码学习_【Python学习】Python源码阅读(一)
最近想读读Python源码,任何东西学习方法基本都是一样的,先从总体框架进行了解,再从自己侧重的方面逐步深入. 1. Python总体架构 左边是Python提供的大量的模块.库以及用户自定义的模块. ...
- Python 源码学习:类型和对象
Python 是一门解释型,动态类型,多范式的编程语言,当我们从 python.org 下载并安装运行 Python 的某个分发版本时,我们实际上是在运行由 C 语言编写的 CPython ,除此之外 ...
- Python源码学习笔记:Python程序执行过程与字节码
Python程序执行过程与字节码 注:本篇是根据教程学习记录的笔记,部分内容与教程是相同的,因为转载需要填链接,但是没有,所以填的原创,如果侵权会直接删除. 问题: 我们每天都要编写一些Python程 ...
- Python源码学习:多线程实现机制
Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 本文分析Python中的多线程机制,主要通过一个多线程的脚本来分析多线程的基本操作与 ...
最新文章
- GAD计算机辅助诊断,GAD-2和GAD-7在心血管门诊焦虑筛查中的信度与效度分析
- Kubernetes基础学习(一)
- Python数据分析笔记——Numpy、Pandas库
- Linux系统编程24:基础IO之在Linux下深刻理解C语言中的动静态库以及头文件和库的关系
- 手工杀毒之“三十六计”
- 使用JavaScript将图片保存至本地
- kotlin的属性委托
- csv转vcf格式网页工具-快速导入手机通讯录
- 数据结构之一元多项式相加
- JavaScript|日期格式化、今天、昨天、明天和某天
- 华硕 X542UQ REV:2.1
- win7电脑变身WiFi热点,让手机、笔记本共享上网
- 业务范围(business area)
- HTML css jQuery实现导航栏(华丽动画)
- 数据结构题集(严书)查找 常见习题代码
- 图像采集卡的种类和区别
- 微信支付踩坑合集:微信小程序支付失败是什么原因?
- SQL总结--存储过程
- 在物联网中保持数据合规
- 排序算法--快速排序(QuickSort)、 3区快速排序(3 Way QuickSort)原理、适用场景及代码示例
热门文章
- GPT-3再进化:通过浏览网页来提高事实准确率
- 谷歌编程语言年度榜NO.1:知识体系总结(2021版)
- 华为昇腾师资培训沙龙·南京场 |华为昇腾 ACL 语言开发实践全程干货来了!看完就实操系列...
- 算法对建筑业的影响,不仅仅是画图
- RANet : 分辨率自适应网络效果和性能的best trade-off | CVPR 2020
- 节后招人平均工资9000上热搜,为什么有些人去哪里都值钱?
- 流行于机器学习竞赛的Boosting,这篇文章讲的非常全了
- ICCV 2019 | 无需数据集的Student Networks
- 喜得爱女,吴恩达深情撰文:欢迎你来到新世界!
- 最新机器学习开源项目Top10