我们每天都要编写一些 Python 程序,或者用来处理一些文本,或者是做一些系统管理工作。程序写好后,只需敲下 python 命令,便可将程序启动起来并开始执行:
$ python some-program.py
$ python some-program.py
那么,一个文本形式的 .py 文件,是如何一步步转换为能够被 CPU 执行的机器指令的呢?此外,程序执行过程中可能会有 .pyc 文件生成,这些文件又有什么作用呢?带着这些问题我们开始本节的探索。
Python程序执行过程
你也许听过这样的说法: Python 是一种解释性语言。这意味着 Python 程序不用编译,只需要用一个解释器来执行。事实真的是这样吗?
虽然从行为上看 Python 更像 Shell 脚本这样的解释性语言,但实际上 Python 程序执行原理本质上跟 Java 或者 C# 一样,都可以归纳为 虚拟机 和 字节码 。Python 执行程序分为两步:先将程序代码编译成字节码,然后启动虚拟机执行字节码:
虽然 python 命令也叫做 Python 解释器 ( Interpreter ),但跟其他脚本语言解释器有本质区别。实际上, Python 解释器包含 编译器 以及 虚拟机 两部分。当 Python 解释器启动后,主要执行以下两个步骤:
- 编译器 将 .py 文件中的 Python 源码编译成 字节码 ;
- 虚拟机 逐行执行编译器生成的 字节码 ;
因此, .py 文件中的 Python 语句并没有直接转换成机器指令,而是转换成 Python 字节码 。
字节码
好了,我们知道 Python 程序的 编译结果 是字节码,里面应该藏着不少 Python 运行的秘密。因此,不管是为了更深入理解 Python 虚拟机运行机制,还是为了调优 Python 程序运行效率,字节码都是绕不过去的一关。那么, Python 字节码到底长啥样呢?我们如何才能获得一个 Python 程序的字节码呢?
为了回答以上问题,我们需要深入 Python 解释器源码,研究 Python 编译器 。但出于几方面考虑,我不打算深入介绍 Python 编译器:① Python 编译器工作原理与其他任何语言类似,市面上任何一本编译原理均有介绍;② 编译原理是计算机基础学科,不是 Python 特有的,不在本专栏的篇幅内;③ 能够影响 Python 编译过程的手段非常有限,研究 Python 编译器对开发工作帮助不大。因此,我们只需要知道 Python 解释器背后有一个编译器负责将源码编译成字节码即可, 字节码以及虚拟机才是我们重点研究的对象 。
那我们还怎么研究字节码呀?别急, Python 提供了一个内置函数 compile 用于即时编译源码。我们只需将待编译源码作为参数调用 compile 函数,即可获得源码的编译结果。
源码编译
接下来,我们调用 compile 函数编译一个例子程序,以此演示该函数的用法:
PI = 3.14
def circle_area(r):
return PI * r ** 2
class Dog(object):
def __init__(self, name):
self.name = name
def yelp(self):
print('woof, i am', self.name)
PI = 3.14
def circle_area(r):
return PI * r ** 2
class Dog(object):
def __init__(self, name):
self.name = name
def yelp(self):
print('woof, i am', self.name)
假设这段源码保存于 demo.py 文件,开始编译之前需要将源码从文件中读取出来:
>>> text = open('demo.py').read()
>>> print(text)
PI = 3.14
def circle_area(r):
return PI * r ** 2
class Dog(object):
def __init__(self, name):
self.name = name
def yelp(self):
print('woof, i am', self.name)
>>> text = open('demo.py').read()
>>> print(text)
PI = 3.14
def circle_area(r):
return PI * r ** 2
class Dog(object):
def __init__(self, name):
self.name = name
def yelp(self):
print('woof, i am', self.name)
接着,调用 compile 函数编译源码:
>>> result = compile(text, 'demo.py', 'exec')
>>> result = compile(text, 'demo.py', 'exec')
compile 函数必填的参数有 3 个:
- source ,待编译 源码 ;
- filename ,源码所在 文件名 ;
- mode , 编译模式 , exec 表示将源码当做一个模块来编译;
顺便提一下, compile 函数有 3 种不同的 编译模式 可供选择:
- exec ,用于编译模块源码;
- single ,用于编译一个单独的 Python 语句(交互式下);
- eval ,用于编译一个 eval 表达式;
compile 详细用法请参考 Python 文档,运行 help 内建函数可快速查看:
>>> help(compile)
>>> help(compile)
我们接着看源码编译结果到底是个什么东西:
>>> result
at 0x103d21150, file "demo.py", line 1>
>>> result.__class__
<class 'code'>
>>> result
at 0x103d21150, file "demo.py", line 1>
>>> result.__class__
<class 'code'>
at 0x103d21150, file "demo.py", line 1>
>>> result.__class__
<class 'code'>
看上去我们得到了一个 代码对象 ,代码对象有什么特别的呢?字节码又是藏身何处呢?想要获取更多详情,请点击“
? 原创不易,求赞,求在看