初步探究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 相关文章推荐
使用Python实现BT种子和磁力链接的相互转换
Nov 09 Python
tensorflow实现softma识别MNIST
Mar 12 Python
python统计中文字符数量的两种方法
Jan 31 Python
元组列表字典(莫烦python基础)
Apr 03 Python
详解利用Python scipy.signal.filtfilt() 实现信号滤波
Jun 05 Python
Django实现发送邮件功能
Jul 18 Python
浅谈Django中view对数据库的调用方法
Jul 18 Python
python求平均数、方差、中位数的例子
Aug 22 Python
Django项目中使用JWT的实现代码
Nov 04 Python
Pycharm插件(Grep Console)自定义规则输出颜色日志的方法
May 27 Python
python3.7中安装paddleocr及paddlepaddle包的多种方法
Nov 27 Python
python实现socket简单通信的示例代码
Apr 13 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
世界上第一台立体声收音机
2021/03/01 无线电
PHP中GET变量的使用
2006/10/09 PHP
PHP输出英文时间日期的安全方法(RFC 1123格式)
2014/06/13 PHP
php 多继承的几种常见实现方法示例
2019/11/18 PHP
JavaScript 注册事件代码
2011/01/27 Javascript
JavaScript高级程序设计(第3版)学习笔记5 js语句
2012/10/11 Javascript
javascript+xml实现简单图片轮换(只支持IE)
2012/12/23 Javascript
javascript快速排序算法详解
2014/09/17 Javascript
jQuery中:submit选择器用法实例
2015/01/03 Javascript
JS实现FLASH幻灯片图片切换效果的方法
2015/03/04 Javascript
JavaScript设计模式学习之“类式继承”
2015/03/12 Javascript
js修改onclick动作的四种方法(推荐)
2016/08/18 Javascript
Nodejs多站点切换Htpps协议详解及简单实例
2017/02/23 NodeJs
Vue.js组件通信的几种姿势
2017/10/23 Javascript
微信小程序 功能函数小结(手机号验证*、密码验证*、获取验证码*)
2017/12/08 Javascript
JS原型对象操作实例分析
2020/06/06 Javascript
vue中el-input绑定键盘按键(按键修饰符)
2020/07/22 Javascript
js获取url页面id,也就是最后的数字文件名
2020/09/25 Javascript
用JavaScript实现贪吃蛇游戏
2020/10/23 Javascript
[01:04:02]DOTA2-DPC中国联赛 正赛 Elephant vs IG BO3 第二场 1月24日
2021/03/11 DOTA
朴素贝叶斯算法的python实现方法
2014/11/18 Python
Python简单定义与使用二叉树示例
2018/05/11 Python
Python时间序列处理之ARIMA模型的使用讲解
2019/04/02 Python
美国折扣宠物药房:Total Pet Supply
2018/05/27 全球购物
Ajax请求总共有多少种Callback
2016/07/17 面试题
餐饮部总监岗位职责范文
2014/02/13 职场文书
《小池塘》教学反思
2014/02/28 职场文书
产品质量承诺书范文
2014/03/27 职场文书
《长城和运河》教学反思
2014/04/14 职场文书
2014医学院领导班子对照检查材料思想汇报
2014/09/19 职场文书
2014年卫生院工作总结
2014/12/03 职场文书
2014年领导班子工作总结
2014/12/11 职场文书
离婚财产分割协议书
2015/08/11 职场文书
中学校园广播稿
2015/08/18 职场文书
分享几个JavaScript运算符的使用技巧
2021/04/24 Javascript
JS class语法糖的深入剖析
2022/07/07 Javascript