首页 > 编程 > Python > 正文

深入Python解释器理解Python中的字节码

2020-02-23 00:28:46
字体:
来源:转载
供稿:网友

我最近在参与Python字节码相关的工作,想与大家分享一些这方面的经验。更准确的说,我正在参与2.6到2.7版本的CPython解释器字节码的工作。

Python是一门动态语言,在命令行工具下运行时,本质上执行了下面的步骤:

    当第一次执行到一段代码时,这段代码会被编译(如,作为一个模块加载,或者直接执行)。根据操作系统的不同,这一步生成后缀名是pyc或者pyo的二进制文件。     解释器读取二进制文件,并依次执行指令(opcodes)。

Python解释器是基于栈的。要理解数据流向,我们需要知道每条指令的栈效应(如,操作码和参数)。

探索Python二进制文件

得到一个二进制文件字节码的最简单方式,是对CodeType结构进行解码:
 

import marshalfd = open('path/to/my.pyc', 'rb')magic = fd.read(4) # 魔术数,与python版本相关date = fd.read(4) # 编译日期code_object = marshal.load(fd)fd.close()

code_object包含了一个CodeType对象,它代表被加载文件的整个模块。为了查看这个模块的类定义、方法等的所有嵌套编码对象(编码对象,原文为code object),我们需要递归地检查CodeType的常量池。就像下面的代码:
 

import types def inspect_code_object(co_obj, indent=''):print indent, "%s(lineno:%d)" % (co_obj.co_name, co_obj.co_firstlineno)for c in co_obj.co_consts:if isinstance(c, types.CodeType):inspect_code_object(c, indent + ' ') inspect_code_object(code_object) # 从第一个对象开始

这个案例中,我们打印出一颗编码对象树,每个编码对象是其父对象的子节点。对下面的代码:
 

class A:def __init__(self):passdef __repr__(self):return 'A()'a = A()print a

我们得到的树形结果是:

<module>(lineno:2) A(lineno:2) __init__(lineno:3) __repr__(lineno:5)

为了测试,我们可以通过compile指令,编译一个包含Python源码的字符串,从而能够得到一个编码对象:
 

co_obj = compile(python_source_code, '<string>', 'exec')

要获取更多关于编码对象的信息,我们可以查阅Python文档的co_* fields 部分。

初见字节码

一旦我们得到了编码对象,我们就可以开始对它进行拆解了(在co_code字段)。从字节码中解析出它的含义:

? 解释操作码的含义 ? 提取任意参数

dis模块的disassemble函数展示了是如何做到的。对我们前面例子,它输出的结果是:

2 0 LOAD_CONST 0 ('A') 3 LOAD_CONST 3 (()) 6 LOAD_CONST 1 (<code object A at 0x42424242, file "<string>", line 2>) 9 MAKE_FUNCTION 0 12 CALL_FUNCTION 0 15 BUILD_CLASS 16 STORE_NAME 0 (A) 8 19 LOAD_NAME  0 (A) 22 CALL_FUNCTION 0 25 STORE_NAME 1 (a) 9 28 LOAD_NAME  1 (a) 31 PRINT_ITEM 32 PRINT_NEWLINE 33 LOAD_CONST 2 (None) 36 RETURN_VALUE            
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表