了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的。

如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代码文件——它们的名字以 .py 结尾。你可能还看到过其它类型的文件,比如以 .pyc 结尾的文件,或许你可能听说过它们就是 Python 的 “ 字节码(bytecode)” 文件。(在 Python 3 上这些可能不容易看到 —— 因为它们与你的 .py 文件不在同一个目录下,它们在一个叫 __pycache__ 的子目录中)或者你也听说过,这是节省时间的一种方法,它可以避免每次运行 Python 时去重新解析源代码。

但是,除了 “噢,原来这就是 Python 字节码” 之外,你还知道这些文件能做什么吗?以及 Python 是如何使用它们的?

01 Python 如何工作

Python 经常被介绍为它是一个解释型语言 —— 其中一个原因是在程序运行时,你的源代码被转换成 CPU 的原生指令 —— 但这样的看法只是部分正确。Python 与大多数解释型语言一样,确实是将源代码编译为一组虚拟机指令,并且 Python 解释器是针对相应的虚拟机实现的。这种中间格式被称为 “字节码”。

因此,这些 .pyc 文件是 Python 悄悄留下的,是为了让它们运行的 “更快”,或者是针对你的源代码的 “优化” 版本;它们是你的程序在 Python 虚拟机上运行的字节码指令。

我们来看一个示例。这里是用 Python 写的经典程序 “Hello, World!”:

def hello()

print("Hello, World!")

下面是转换后的字节码(转换为人类可读的格式):

2 0 LOAD_GLOBAL 0 (print)

2 LOAD_CONST 1 ('Hello, World!')

4 CALL_FUNCTION 1

如果你输入那个 hello() 函数,然后使用 CPython 解释器去运行它,那么上述列出的内容就是 Python 所运行的。它看起来可能有点奇怪,因此,我们来深入了解一下它都做了些什么。

02 Python 虚拟机内幕

CPython 使用一个基于栈的虚拟机。也就是说,它完全面向栈数据结构的(你可以 “推入” 一个东西到栈 “顶”,或者,从栈 “顶” 上 “弹出” 一个东西来)。

CPython 使用三种类型的栈:

  1. 调用栈(call stack)。这是运行 Python 程序的主要结构。它为每个当前活动的函数调用使用了一个东西 —— “ 帧(frame)”,栈底是程序的入口点。每个函数调用推送一个新的帧到调用栈,每当函数调用返回后,这个帧被销毁。
  2. 在每个帧中,有一个 计算栈(evaluation stack) (也称为 数据栈(data stack))。这个栈就是 Python 函数运行的地方,运行的 Python 代码大多数是由推入到这个栈中的东西组成的,操作它们,然后在返回后销毁它们。
  3. 在每个帧中,还有一个 块栈(block stack)。它被 Python 用于去跟踪某些类型的控制结构:循环、try / except 块、以及 with 块,全部推入到块栈中,当你退出这些控制结构时,块栈被销毁。这将帮助 Python 了解任意给定时刻哪个块是活动的,比如,一个 continue 或者 break 语句可能影响正确的块。

大多数 Python 字节码指令操作的是当前调用栈帧的计算栈,虽然,还有一些指令可以做其它的事情(比如跳转到指定指令,或者操作块栈)。

为了更好地理解,假设我们有一些调用函数的代码,比如这个:my_function(my_variable, 2)。Python 将转换为一系列字节码指令:

  1. 一个 LOAD_NAME 指令去查找函数对象 my_function,然后将它推入到计算栈的顶部
  2. 另一个 LOAD_NAME 指令去查找变量 my_variable,然后将它推入到计算栈的顶部
  3. 一个 LOAD_CONST 指令去推入一个实整数值 2 到计算栈的顶部
  4. 一个 CALL_FUNCTION 指令

这个 CALL_FUNCTION 指令将有 2 个参数,它表示那个 Python 需要从栈顶弹出两个位置参数;然后函数将在它上面进行调用,并且它也同时被弹出(对于函数涉及的关键字参数,它使用另一个不同的指令 —— CALL_FUNCTION_KW,但使用的操作原则类似,以及第三个指令 —— CALL_FUNCTION_EX,它适用于函数调用涉及到参数使用 * 或 ** 操作符的情况)。一旦 Python 拥有了这些之后,它将在调用栈上分配一个新帧,填充到函数调用的本地变量上,然后,运行那个帧内的 my_function 字节码。运行完成后,这个帧将被调用栈销毁,而在最初的帧内,my_function 的返回值将被推入到计算栈的顶部。

03 访问和理解 Python 字节码

如果你想玩转字节码,那么,Python 标准库中的 dis 模块将对你有非常大的帮助;dis 模块为 Python 字节码提供了一个 “反汇编”,它可以让你更容易地得到一个人类可读的版本,以及查找各种字节码指令。 dis 模块的文档 可以让你遍历它的内容,并且提供一个字节码指令能够做什么和有什么样的参数的完整清单。

例如,获取上面的 hello() 函数的列表,可以在一个 Python 解析器中输入如下内容,然后运行它:

import dis

dis.dis(hello)

函数 dis.dis() 将反汇编一个函数、方法、类、模块、编译过的 Python 代码对象、或者字符串包含的源代码,以及显示出一个人类可读的版本。dis 模块中另一个方便的功能是 distb()。你可以给它传递一个 Python 追溯对象,或者在发生预期外情况时调用它,然后它将在发生预期外情况时反汇编调用栈上最顶端的函数,并显示它的字节码,以及插入一个指向到引发意外情况的指令的指针。

它也可以用于查看 Python 为每个函数构建的编译后的代码对象,因为运行一个函数将会用到这些代码对象的属性。这里有一个查看 hello() 函数的示例:

>>> hello.__code__

<code object hello at 0x104e46930, file "<stdin>", line 1>

>>> hello.__code__.co_consts

(None, 'Hello, World!')

>>> hello.__code__.co_varnames

()

>>> hello.__code__.co_names

('print',)

代码对象在函数中可以以属性 __code__ 来访问,并且携带了一些重要的属性:

  • co_consts 是存在于函数体内的任意实数的元组
  • co_varnames 是函数体内使用的包含任意本地变量名字的元组
  • co_names 是在函数体内引用的任意非本地名字的元组

许多字节码指令 —— 尤其是那些推入到栈中的加载值,或者在变量和属性中的存储值 —— 在这些元组中的索引作为它们参数。

因此,现在我们能够理解 hello() 函数中所列出的字节码:

  1. LOAD_GLOBAL 0:告诉 Python 通过 co_names (它是 print 函数)的索引 0 上的名字去查找它指向的全局对象,然后将它推入到计算栈
  2. LOAD_CONST 1:带入 co_consts 在索引 1 上的字面值,并将它推入(索引 0 上的字面值是 None,它表示在 co_consts 中,因为 Python 函数调用有一个隐式的返回值 None,如果没有显式的返回表达式,就返回这个隐式的值 )。
  3. CALL_FUNCTION 1:告诉 Python 去调用一个函数;它需要从栈中弹出一个位置参数,然后,新的栈顶将被函数调用。

“原始的” 字节码 —— 是非人类可读格式的字节 —— 也可以在代码对象上作为 co_code 属性可用。如果你有兴趣尝试手工反汇编一个函数时,你可以从它们的十进制字节值中,使用列出 dis.opname 的方式去查看字节码指令的名字。

04 字节码的用处

现在,你已经了解的足够多了,你可能会想 “OK,我认为它很酷,但是知道这些有什么实际价值呢?”由于对它很好奇,我们去了解它,但是除了好奇之外,Python 字节码在几个方面还是非常有用的。

首先,理解 Python 的运行模型可以帮你更好地理解你的代码。人们都开玩笑说,C 是一种 “可移植汇编器”,你可以很好地猜测出一段 C 代码转换成什么样的机器指令。理解 Python 字节码之后,你在使用 Python 时也具备同样的能力 —— 如果你能预料到你的 Python 源代码将被转换成什么样的字节码,那么你可以知道如何更好地写和优化 Python 源代码。

第二,理解字节码可以帮你更好地回答有关 Python 的问题。比如,我经常看到一些 Python 新手困惑为什么某些结构比其它结构运行的更快(比如,为什么 {} 比 dict() 快)。知道如何去访问和阅读 Python 字节码将让你很容易回答这样的问题(尝试对比一下: dis.dis("{}") 与 dis.dis("dict()") 就会明白)。

最后,理解字节码和 Python 如何运行它,为 Python 程序员不经常使用的一种特定的编程方式提供了有用的视角:面向栈的编程。如果你以前从来没有使用过像 FORTH 或 Fator 这样的面向栈的编程语言,它们可能有些古老,但是,如果你不熟悉这种方法,学习有关 Python 字节码的知识,以及理解面向栈的编程模型是如何工作的,将有助你开拓你的编程视野。

 
 
 
 

Python 字节码是什么的更多相关文章

  1. 浮生半日:探究Python字节码

    好吧!“人生苦短,请用Python”,作为python爱好者以及安全从业者,而且最近也碰到了一些这方面的问题,懂点python字节码还是很有必要的. Python是一门解释性语言,它的具体工作流程如下 ...

  2. Python逆向(五)—— Python字节码解读

    一.前言 前些章节我们对python编译.反汇编的原理及相关模块已经做了解读.读者应该初步掌握了通过反汇编获取python程序可读字节码的能力.python逆向或者反汇编的目的就是在没有源码的基础上, ...

  3. Python 字节码bytecode

    字节码bytecode python把源码文件编译成字节码文件,存放在__pycahe子目录内,用.pyc结尾.之后如果不再修改源码文件,运行时则使用*.pyc文件编译成机器码,这样不但运行速度快,而 ...

  4. Python字节码与解释器学习

    参考:http://blog.jobbole.com/55327/ http://blog.jobbole.com/56300/ http://blog.jobbole.com/56761/ 1. 在 ...

  5. Python字节码介绍

    了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的.如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代码 ...

  6. 使用uncompyle2直接反编译python字节码文件pyo/pyc

    update:在Mac OS X版的September 10, 2014版(5.0.9-1)中发现安装目录中的src.zip已更换位置至WingIDE.app/Contents/Resources/b ...

  7. python 字节码死磕

    前言:  如果你跟我一样,对python的字节码感兴趣,想了解python的代码在内存中到底是怎么去运行的,那么你可以继续往下看,如果你是python新手,我建议你移步它处,本文适合有点基础的pyth ...

  8. python字节码,java字节码,十六进制相互转换

    下面是互相转换的代码: 有想要了解更多关于python知识的请在下方评论或私信小编

  9. python反编译之字节码

    如果你曾经写过或者用过 Python,你可能已经习惯了看到 Python 源代码文件:它们的名称以.Py 结尾.你可能还见过另一种类型的文件是 .pyc 结尾的,它们就是 Python "字 ...

随机推荐

  1. UWP 轨道视图Orbit View

    先看一下效果吧 这是我的Music Tags App里面的效果图,当然你也可以做的比我的更炫. 其实这个效果的实现来自于控件UWP Community Toolkit的OrbitView,所以大家要多 ...

  2. async/await工作机制探究--NodeJS

    ES6中的async/await让Promise变得更加简便,通常await处理的链式Promise会包裹在函数中,返回结果仍然是一个Promise对象. 但是当await直接处理链式Promise时 ...

  3. JMeter各个基础组件简介

    刚从LoadRunner转到JMeter,对JMeter的各种概念比较懵.在这里记录下.欢迎大家关注我的个人微信号:测试杂货铺. JMeter的各个功能都是它的组件来完成或实现的,下面来对JMeter ...

  4. MySQL5.7(二)数据库的基本操作

    登录MySQL数据库 格式:mysql -u 用户名 -h 主机名或IP地址  -P 端口号 -p 密码

  5. Python之pexpect详解

    一.引子 Pexpect程序主要用于人机对话的模拟,就是那种系统提问,人来回答yes/no,或者账号登陆输入用户名和密码等等的情况.因为这种情况特别多而且繁琐,所以很多语言都有各种自己的实现.最初的第 ...

  6. Python机器学习库SKLearn:数据集转换之管道和特征

    转载自:https://blog.csdn.net/cheng9981/article/details/61918129 4.1 管道和特征:组合估计量 4.1.1 管道:链接估计 管道可以用于将多个 ...

  7. Python科学测量与计算库Pymeasure: 控制你的仪器进行自动测试和科学计算

    Python这种脚本语言因其语法简单,工具包丰富成熟,使用起来非常方便.在很多领域被广泛使用,今天介绍的是python在仪器控制应用领域,python在仪器控制领域相关的书籍可以参考<真实世界的 ...

  8. kafka handler

    1.配置kafka 参数文件 在ogg主目录下有示例文件: [root@WH0PRDBRP00AP0013 ogg]# cd AdapterExamples/big-data/kafka/ [root ...

  9. css修改input自动提示的黄色背景

    css修改input自动提示的黄色背景 input:-webkit-autofill { background-color: #FAFFBD; background-image: none; -web ...

  10. (第十周)评论Beta发布

    本人所在组:奋斗吧兄弟 按课上展示的顺序对每组进行点评: 1.  飞天小女警 项目:礼物挑选工具 相对于alpha发布时有了很大的进步.项目的界面很漂亮,这个项目的想法很新颖,我很喜欢.礼物的挑选给出 ...