初步探究Python程序的执行原理


Posted in Python onApril 11, 2015

1. 过程概述

Python先把代码(.py文件)编译成字节码,交给字节码虚拟机,然后虚拟机一条一条执行字节码指令,从而完成程序的执行。
2. 字节码

字节码在Python虚拟机程序里对应的是PyCodeObject对象。
.pyc文件是字节码在磁盘上的表现形式。
3. pyc文件

PyCodeObject对象的创建时机是模块加载的时候,即import。
Python test.py会对test.py进行编译成字节码并解释执行,但是不会生成test.pyc。
如果test.py加载了其他模块,如import util,Python会对util.py进行编译成字节码,生成util.pyc,然后对字节码解释执行。
如果想生成test.pyc,我们可以使用Python内置模块py_compile来编译。
加载模块时,如果同时存在.py和.pyc,Python会尝试使用.pyc,如果.pyc的编译时间早于.py的修改时间,则重新编译.py并更新.pyc。
4. PyCodeObject

Python代码的编译结果就是PyCodeObject对象。

typedef struct {
  PyObject_HEAD
  int co_argcount;    /* 位置参数个数 */
  int co_nlocals;     /* 局部变量个数 */
  int co_stacksize;    /* 栈大小 */
  int co_flags;  
  PyObject *co_code;   /* 字节码指令序列 */
  PyObject *co_consts;  /* 所有常量集合 */
  PyObject *co_names;   /* 所有符号名称集合 */
  PyObject *co_varnames; /* 局部变量名称集合 */
  PyObject *co_freevars; /* 闭包用的的变量名集合 */
  PyObject *co_cellvars; /* 内部嵌套函数引用的变量名集合 */
  /* The rest doesn't count for hash/cmp */
  PyObject *co_filename; /* 代码所在文件名 */
  PyObject *co_name;   /* 模块名|函数名|类名 */
  int co_firstlineno;   /* 代码块在文件中的起始行号 */
  PyObject *co_lnotab;  /* 字节码指令和行号的对应关系 */
  void *co_zombieframe;  /* for optimization only (see frameobject.c) */
} PyCodeObject;
 
typedef struct {
  PyObject_HEAD
  int co_argcount;    /* 位置参数个数 */
  int co_nlocals;     /* 局部变量个数 */
  int co_stacksize;    /* 栈大小 */
  int co_flags;  
  PyObject *co_code;   /* 字节码指令序列 */
  PyObject *co_consts;  /* 所有常量集合 */
  PyObject *co_names;   /* 所有符号名称集合 */
  PyObject *co_varnames; /* 局部变量名称集合 */
  PyObject *co_freevars; /* 闭包用的的变量名集合 */
  PyObject *co_cellvars; /* 内部嵌套函数引用的变量名集合 */
  /* The rest doesn't count for hash/cmp */
  PyObject *co_filename; /* 代码所在文件名 */
  PyObject *co_name;   /* 模块名|函数名|类名 */
  int co_firstlineno;   /* 代码块在文件中的起始行号 */
  PyObject *co_lnotab;  /* 字节码指令和行号的对应关系 */
  void *co_zombieframe;  /* for optimization only (see frameobject.c) */
} PyCodeObject;

5. pyc文件格式

加载模块时,模块对应的PyCodeObject对象被写入.pyc文件,格式如下:

初步探究Python程序的执行原理

6. 分析字节码
6.1 解析PyCodeObject

Python提供了内置函数compile可以编译Python代码和查看PyCodeObject对象,如下:

Python代码[test.py]

s = ”hello”

def func():
  print s

func()
 
s = ”hello”
 
def func():
  print s
 
func()

 

在Python交互式shell里编译代码得到PyCodeObject对象:

初步探究Python程序的执行原理

dir(co)已经列出co的各个域,想查看某个域直接在终端输出即可:

初步探究Python程序的执行原理

test.py的PyCodeObject

co.co_argcount  0
co.co_nlocals   0
co.co_names    (‘s', 'func')
co.co_varnames  (‘s', 'func')
co.co_consts   (‘hello', <code object func at 0x2aaeeec57110, file ”test.py”, line 3>, None)
co.co_code    'd\x00\x00Z\x00\x00d\x01\x00\x84\x00\x00Z\x01\x00e\x01\x00\x83\x00\x00\x01d\x02\x00S'
 
co.co_argcount  0
co.co_nlocals   0
co.co_names    (‘s', 'func')
co.co_varnames  (‘s', 'func')
co.co_consts   (‘hello', <code object func at 0x2aaeeec57110, file ”test.py”, line 3>, 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对象,见上面的co_consts[1]

func的PyCodeObject

func.co_argcount  0
func.co_nlocals  0
func.co_names   (‘s',)
func.co_varnames  ()
func.co_consts   (None,)
func.co_code    ‘t\x00\x00GHd\x00\x00S'
 
func.co_argcount  0
func.co_nlocals  0
func.co_names   (‘s',)
func.co_varnames  ()
func.co_consts   (None,)
func.co_code    ‘t\x00\x00GHd\x00\x00S'

 

co_code是指令序列,是一串二进制流,它的格式和解析方法见6.2。
6.2 解析指令序列

指令序列co_code的格式

初步探究Python程序的执行原理

Python内置的dis模块可以解析co_code,如下图:

test.py的指令序列

初步探究Python程序的执行原理

func函数的指令序列

初步探究Python程序的执行原理

第一列表示以下几个指令在py文件中的行号;
第二列是该指令在指令序列co_code里的偏移量;
第三列是指令opcode的名称,分为有操作数和无操作数两种,opcode在指令序列中是一个字节的整数;
第四列是操作数oparg,在指令序列中占两个字节,基本都是co_consts或者co_names的下标;
第五列带括号的是操作数说明。
7. 执行字节码

Python虚拟机的原理就是模拟可执行程序再X86机器上的运行,X86的运行时栈帧如下图:

初步探究Python程序的执行原理

假如test.py用C语言来实现,会是下面这个样子:

const char *s = “hello”;

void func() {
  printf(“%s\n”, s);
}

int main() {
  func();
  return 0;
}
 
const char *s = “hello”;
 
void func() {
  printf(“%s\n”, s);
}
 
int main() {
  func();
  return 0;
}

 

Python虚拟机的原理就是模拟上述行为。当发生函数调用时,创建新的栈帧,对应Python的实现就是PyFrameObject对象。
7.1 PyFrameObject

typedef struct _frame {
  PyObject_VAR_HEAD
  struct _frame *f_back;  /* 调用者的帧 */
  PyCodeObject *f_code;   /* 帧对应的字节码对象 */
  PyObject *f_builtins;   /* 内置名字空间 */
  PyObject *f_globals;   /* 全局名字空间 */
  PyObject *f_locals;    /* 本地名字空间 */
  PyObject **f_valuestack; /* 运行时栈底 */
  PyObject **f_stacktop;  /* 运行时栈顶 */
  …….
}
 
typedef struct _frame {
  PyObject_VAR_HEAD
  struct _frame *f_back;  /* 调用者的帧 */
  PyCodeObject *f_code;   /* 帧对应的字节码对象 */
  PyObject *f_builtins;   /* 内置名字空间 */
  PyObject *f_globals;   /* 全局名字空间 */
  PyObject *f_locals;    /* 本地名字空间 */
  PyObject **f_valuestack; /* 运行时栈底 */
  PyObject **f_stacktop;  /* 运行时栈顶 */
  …….
}

 

那么对应Python的运行时栈就是这样子:

初步探究Python程序的执行原理

7.2 执行指令

执行test.py的字节码时,会先创建一个栈帧,以下用f表示当前栈帧,执行过程注释如下:

test.py的符号名集合和常量集合

co.co_names  (‘s', 'func')
co.co_consts (‘hello', <code object func at 0x2aaeeec57110, file ”test.py”, line 3>, None)
 
co.co_names  (‘s', 'func')
co.co_consts (‘hello', <code object func at 0x2aaeeec57110, file ”test.py”, line 3>, None)

 

test.py的指令序列

初步探究Python程序的执行原理

上面的CALL_FUNCTION指令执行时,会创建新的栈帧,并执行func的字节码指令,以下用f表示当前栈帧,func的字节码执行过程如下:

func函数的符号名集合和常量集合

func.co_names    (‘s',)
func.co_consts   (None,)
 
func.co_names    (‘s',)
func.co_consts   (None,)

func函数的指令序列

初步探究Python程序的执行原理

7.3 查看栈帧

如果你想查看当前栈帧,Python提供了sys._getframe()方法可以获取当前栈帧,你只需要在代码里加入代码如下:

def func():
  import sys
  frame = sys._getframe()
  print frame.f_locals
  print frame.f_globals
  print frame.f_back.f_locals
  #你可以打印frame的各个域
  print s

 

Python 相关文章推荐
详解Django中Request对象的相关用法
Jul 17 Python
python实现ID3决策树算法
Dec 20 Python
Pycharm在创建py文件时,自动添加文件头注释的实例
May 07 Python
Python应用库大全总结
May 30 Python
python 定时任务去检测服务器端口是否通的实例
Jan 26 Python
python版DDOS攻击脚本
Jun 12 Python
在python中实现调用可执行文件.exe的3种方法
Jul 07 Python
pytorch 自定义数据集加载方法
Aug 18 Python
python判断链表是否有环的实例代码
Jan 31 Python
推荐8款常用的Python GUI图形界面开发框架
Feb 23 Python
django执行原始查询sql,并返回Dict字典例子
Apr 01 Python
Python将字典转换为XML的方法
Aug 01 Python
Python与shell的3种交互方式介绍
Apr 11 #Python
Python函数参数类型*、**的区别
Apr 11 #Python
Python中的多重装饰器
Apr 11 #Python
Python中的各种装饰器详解
Apr 11 #Python
将Django使用的数据库从MySQL迁移到PostgreSQL的教程
Apr 11 #Python
Python返回真假值(True or False)小技巧
Apr 10 #Python
Python选择排序、冒泡排序、合并排序代码实例
Apr 10 #Python
You might like
PHP MVC模式在网站架构中的实现分析
2010/03/04 PHP
解析关于wamp启动是80端口被占用的问题
2013/06/21 PHP
PHP中Http协议post请求参数
2015/11/02 PHP
php实现按天数、星期、月份查询的搜索框
2016/05/02 PHP
浅谈PHP错误类型及屏蔽方法
2017/05/27 PHP
php获取ajax的headers方法与内容实例
2017/12/27 PHP
php语言注释,单行注释和多行注释
2018/01/21 PHP
JS常用正则表达式总结
2013/11/12 Javascript
解决jquery版本冲突的有效方法
2014/09/02 Javascript
基于jquery css3实现点击动画弹出表单源码特效
2015/08/31 Javascript
怎么限制input的text里输入的值只能是数字(正则、js)
2016/05/16 Javascript
JS在Chrome浏览器中showModalDialog函数返回值为undefined的解决方法
2016/08/03 Javascript
jQuery Ajax前后端使用JSON进行交互示例
2017/03/17 Javascript
详解node.js平台下Express的session与cookie模块包的配置
2017/04/26 Javascript
vue.js todolist实现代码
2017/10/29 Javascript
javascript回调函数详解
2018/02/06 Javascript
快速解决处理后台返回json数据格式的问题
2018/08/07 Javascript
详解解决Vue相同路由参数不同不会刷新的问题
2018/10/12 Javascript
JS实现排行榜文字向上滚动轮播效果
2019/11/26 Javascript
微信小程序swiper使用网络图片不显示问题解决
2019/12/13 Javascript
[02:37]2015国际邀请赛选手档案—LGD.Xiao8
2015/07/28 DOTA
python并发和异步编程实例
2018/11/15 Python
对python 自定义协议的方法详解
2019/02/13 Python
Python使用sqlalchemy模块连接数据库操作示例
2019/03/13 Python
python tornado使用流生成图片的例子
2019/11/18 Python
Django项目基础配置和基本使用过程解析
2019/11/25 Python
基于打开pycharm有带图片md文件卡死问题的解决
2020/04/24 Python
pandas数据选取:df[] df.loc[] df.iloc[] df.ix[] df.at[] df.iat[]
2020/04/24 Python
浅谈CSS3中的变形功能-transform功能
2017/12/27 HTML / CSS
AP澳洲中文网:澳洲正品直邮,包税收件无忧
2019/07/12 全球购物
一套带答案的C++笔试题
2014/01/10 面试题
一年级评语大全
2014/04/23 职场文书
2014年维修工作总结
2014/11/22 职场文书
追悼会家属答谢词
2015/09/29 职场文书
团支部组织委员竞选稿
2015/11/21 职场文书
go语言map与string的相互转换的实现
2021/04/07 Golang