当前位置: 首页 > 科技观察

Python程序的执行原理

时间:2023-03-18 13:01:56 科技观察

1.流程概述Python首先将代码(.py文件)编译成字节码,发送给字节码虚拟机,然后由虚拟机一条一条执行字节码指令,完成程序。实施。2.字节码字节码对应Python虚拟机程序中的PyCodeObject对象。.pyc文件是字节码在磁盘上的表示形式。3、pyc文件中PyCodeObject对象的创建时间是模块加载时,即import时。Pythontest.py会将test.py编译成字节码并解释,但不会生成test.pyc。如果test.py加载了其他模块,比如importutil,Python会把util.py编译成字节码,生成util.pyc,然后解释执行字节码。如果我们要生成test.pyc,可以使用Python内置模块py_compile来编译。加载模块时,如果.py和.pyc都存在,Python会尝试使用.pyc,如果.pyc编译早于.py被修改,它会重新编译.py并更新.pyc。4.PyCodeObjectPython代码的编译结果就是PyCodeObject对象。typedefstruct{PyObject_HEADintco_argcount;/*位置参数个数*/intco_nlocals;/*局部变量个数*/intco_stacksize;/*堆栈大小*/intco_flags;PyObject*co_code;/*字节码指令序列*/PyObject*co_consts;/*All常量集合*/PyObject*co_names;/*所有符号名集合*/PyObject*co_varnames;/*局部变量名集合*/PyObject*co_freevars;/*闭包变量名集合*/PyObject*co_cellvars;/*内部嵌套函数引用的变量名集合*//*其余不算hash/cmp*/PyObject*co_filename;/*代码所在文件名*/PyObject*co_name;/*模块名|Functionname|类名*/intco_firstlineno;/*文件中代码块的起始行号*/PyObject*co_lnotab;/*字节码指令与行号的对应关系*/void*co_zombieframe;/*foroptimizationonly(seeframeobject.c)*/}PyCodeObject;5.pyc文件格式加载模块时,将模块对应的PyCodeObject对象写入.pyc文件,格式如下:6.分析字节码6.1分析PyCodeObjectPython提供了一个内置函数compile来编译Python代码并查看PyCodeObject对象,如下:Python代码[test.py]s=”hello”deffunc():printsfunc()在Python交互shell中编译代码得到PyCodeObject对象:dir(co)已经列出了co的字段,如果你想直接在终端输出中查看某个字段就足够了:o.co_consts('hello',,None)co.co_code'd\x00\x00Z\x00\x00d\x01\x00\x84\x00\x00Z\x01\x00e\x01\x00\x83\x00\x00\x01d\x02\x00S'Python解释器也会为函数生成字节码PyCodeObject对象,见PyCodeObjectfunc.co_argcount0func.co_nlocals0func.co_names('s',)func.co_varnames()func。co_consts(None,)func.co_code't\x00\x00GHd\x00\x00S'co_code是一个指令序列,一串二进制流,其格式和解析方法见6.26。2解析指令序列instructionsequenceco_codeformatopcodeopcodeopcodeopcodeoparg…1byte2bytes1byte1byte2bytes#p#test.pyinstructionsequencefuncfunctioninstructionsequence***列表示py后面的指令行号在文件中;第二列是该指令在指令序列co_code中的偏移量;第三列是指令opcode的名称,分为有操作数和无操作数两种,opcode是指令序列中一个字节的整数;第四列是操作数oparg,在指令序列中占两个字节,基本就是co_consts或co_names的下标;带括号的第五列是操作数描述。7、执行字节码Python虚拟机的原理是模拟可执行程序在X86机器上的执行。X86运行时栈帧如下图所示:如果用C语言实现test.py,会是这样:constchar*s="hello";voidfunc(){printf("%s\n",s);}intmain(){func();return0;}Python虚拟机的原理就是模拟上面的行为。当发生函数调用时,会创建一个新的栈帧,对应的Python实现就是PyFrameObject对象。7.1PyFrameObjecttypedefstruct_frame{PyObject_VAR_HEADstruct_frame*f_back;/*调用者的frame*/PyCodeObject*f_code;/*frame对应的字节码对象*/PyObject*f_builtins;/*内置命名空间*/PyObject*f_globals;/*全局命名空间*/PyObject*f_locals;/*localnamespace*/PyObject**f_valuestack;/*runtimestackbottom*/PyObject**f_stacktop;/*runtimestacktop*/…….}然后对应Python的runtimestack是这样的看起来像:7.2执行指令在执行test.py的字节码时,首先会创建一个栈帧,当前栈帧用下面的f表示。执行过程注意事项如下:test.py的符号名集合和常量集合co.co_names('s','func')co.co_consts('hello',,None)当上面的CALL_FUNCTION指令在test.py的指令序列中执行时,会创建一个新的栈帧,并执行func的字节码指令,当前栈帧用下面的f表示,字节码执行过程func的函数如下:func函数的符号名集和常量集func.co_names('s',)func.co_consts(None,)#p#func函数指令序列7.3查看栈帧如果要查看当前栈帧,Python提供了sys._getframe()方法来获取当前栈帧,只需要在代码中加入如下代码:deffunc():importsysframe=sys._getframe()printframe.f_localsprintframe.f_globalsprintframe.f_back。f_locals#可以打印frame的每个字段prints原文链接:http://tech.uc.cn/?p=1932