面试python程序运行机制与pyc文件
Posted 黑黑白白君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试python程序运行机制与pyc文件相关的知识,希望对你有一定的参考价值。
文章目录
Python 是一门解释型的编程语言,因此它具有解释型语言的运行机制。
知道Python运行机制,并不是我们一般人所必须的。但是,了解其加速程序运行以及优化代码的设计思想,对于我们在日后构造缓存系统、如何减少不必要的运行时间,以及同步更新工作内容等问题上起到很大的借鉴作用。
1)高级编程语言的类型
计算机程序,其实就是一组计算机指令集,能真正驱动机器运行的是机器指令,但让普通开发者直接编写机器指令是不现实的,因此就出现了计算机高级语言。
- 高级语言允许使用自然语言(通常就是英语)来编程,但高级语言的程序最终必须被翻译成机器指令来执行。
高级编程语言按程序的执行方式可以分为编译型和解释型两种。
1.1 编译型语言
现有的 C 、C++、Objective-C、Pascal 等高级语言都属于编译型语言。
编译型语言是指使用专门的编译器,针对特定平台(操作系统)将某种高级语言源代码一次性“翻译”成可被该平台硬件执行的机器码(包括机器指令和操作数),并包装成该平台所能识别的可执行程序的格式,这个转换过程称为编译(Compile)。
- 有些程序编译结束后,还可能需要对其他编译好的目标代码进行链接,即组装两个以上的目标代码模块生成最终的可执行程序,通过这种方式实现低层次的代码复用。
- 编译生成的可执行程序可以脱离开发环境,在特定的平台上独立运行。
- 因为编译型语言的程序被编译成特定平台上的机器码,因此编译生成的可执行程序通常无法移植到其他平台上运行,如果需要移植,则必须将源代码复制到特定平台上,针对特定平台进行修改,至少需要采用特定平台上的编译器重新编译。
1.2 解释型语言
解释型语言是指使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行的语言。
- 解释型语言通常不会进行整体性的编译和链接处理,解释型语言相当于把编译型语言中的编译和解释过程混合到一起同时完成。
- 可以认为,每次执行解释型语言的程序都需要进行一次编译,因此解释型语言的程序运行效率通常较低,而且不能脱离解释器独立运行。
- 解释型语言有一个优势,就是跨平台比较容易,只需提供特定平台的解释器即可。
- 每个特定平台上的解释器都负责将源程序解释成特定平台的机器指令。
- 解释型语言可以方便地实现源程序级的移植,但这是以牺牲程序执行效率为代价的。
1.3 编译型语言VS解释型语言
2)Python解释器
Python 语言属于解释型语言,因此运行 Python 程序时需要使用特定的解释器进行解释、执行。
*什么是解释器?
解释器是一种让其他程序运行起来的程序,它是代码与机器的计算机硬件之间的软件逻辑层。
- Python解释器本身也是个程序, 它是解释执行 Python代码的,所以叫解释器。
2.1 python解释器的构成
解释器由一个编译器和一个虚拟机构成。
- 编译器负责将源代码转换成字节码文件
- 而虚拟机负责执行字节码
所以,解释型语言其实也有编译过程,只不过这个编译过程并不是直接生成目标代码,而是中间代码(字节码),然后再通过虚拟机来逐行解释执行字节码。
CPython 具体内容如下:
- Scanner 对应词法分析器,将从文件输入的代码切分为 token
- Parser 对应语法分析器,在Scanner 的分析结果上进行语法分析,建立 AST
- Compiler 根据建立的 AST 生成指令集合 Python 字节码 (这里的 .pyc 文件类似于Java 的 .class 文件)
- 最后由 Code Evaluator 执行代码
- Jython 是把 Interpreter 给重写了,Jvm 此时就是 Code Evaluator
- 解释器中的 Code Evaluator 就是通常所说的 pvm (Python虚拟机)
2.2 python代码执行过程
1、执行 python XX.py 后,将会启动 Python 的解释器。
2、python解释器的编译器会将.py源文件编译(解释)成字节码生成PyCodeObject字节码对象存放在内存中。
- 转换后的字节码不是二进制的机器码,需要进一步编译才能被机器执行,这也是Python代码无法运行的像C/C++ 一样快的原因。
3、python解释器的虚拟机(PVM)将执行内存中的字节码对象转化为机器语言,虚拟机与操作系统交互,使机器语言在机器硬件上运行。
- PVM是 Python Virtual Machine的简称,它是Python的运行引擎。
Python虚拟机的原理就是模拟可执行程序在X86机器上的运行。
- 当发生函数调用时,创建新的栈帧,对应Python的实现就是PyFrameObject对象。
- PyFrameObject对象创建程序运行时的动态信息,即执行环境。
- 每一个 PyFrameObject对象都维护了一个 PyCodeObject对象,这表明每一个 PyFrameObject中的动态内存空间对象都和源代码中的一段Code相对应。
4、程序运行结束后,根据命令行调用情况(即运行程序的方式)决定是否将PyCodeObject写回硬盘当中(也就是直接复制到.pyc或.pyo文件中)。
- 当python程序第二次运行时,首先程序会在硬盘中寻找pyc文件,如果找到,则直接载入,否则就重复上面的过程。
2.2 Python 解释器的种类
Python解释器有三种主要的实现方式,CPython、Jython和IronPython 三种实现方式。
-
CPython
CPython 是由C语言编写的,它是大多数Linux和Mac OS X机器预装的Python解释器,也是所有Python解释器中运行最快、最完整、最健全的。
-
Jython
Jython 是一种Python语言的替代实现方式,其目的是为了与Java编程语言集成。
- Jython 包含了Java类,这些类编译Python源代码、形成Java字节码,并将得到的字节码映射到Java虚拟机(JVM)上。
- 因为Jython要比CPython 慢而且也不够健壮,它往往看作是一个主要面向寻找Java代码前端脚本语言的Java开发者的一个有趣的工具。
-
IronPython
IronPython 设计的目的是让Python 程序可以与Windows 平台上的.NET 框架以及与之对应的Linux的上开源的Mono编写成的应用集成。
-
其他:
- IPython:IPython是基于CPython之上的一个交互式解释器
- PyPy:PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。
3)pyc文件
-
包:一个文件夹,用来存放模块和子包。
- 包里一般会有一个__init__.py 的文件(也可以没有)。
- 包里一般也会有一个__pycache__文件夹,存放 .py 文件经解释器解释后的中间字节码(二进制文件)。
-
模块: 可以作为模块的文件有 .py 、.pyc、.pyo、.pyd、.so、.dll文件。
3.1 什么是pyc文件?
pyc是一种二进制文件,是由py文件经过编译后生成的文件,是一种byte code。
- py文件变成pyc文件后,加载的速度有所提高,而且pyc是一种跨平台的字节码,是由python的虚拟机来执行的。
- pyc的内容,是跟python的版本相关的,不同版本编译后的pyc文件是不同的,2.5编译的pyc文件,2.4版本的python是无法执行的。
pyc文件其实是PyCodeObject的一种持久化保存方式。
- 文件中包含python的magic number(来说明编译时使用的python版本号)、源文件的mtime(使pyc和py文件保持同步)、编译出的code对象。
*什么是PyCodeObject?
- Python代码的编译结果就是PyCodeObject对象。
- PyCodeObject对象中包含了字节码指令以及程序的所有静态信息,但没有包含程序运行时的动态信息——执行环境(PyFrameObject)。
- 字节码在python虚拟机程序里对应的是PyCodeObject对象。
为什么需要pyc?
因为py文件是可以直接看到源码的,如果是开发商业软件的话,不可能把源码泄漏出去,所以就需要编译为pyc后,再发布出去。
- 当然,pyc文件也是可以反编译的,不同版本编译后的pyc文件是不同的。
- 根据python源码中提供的opcode,可以根据pyc文件反编译出py文件源码。
什么时候会生成pyc?
-
pyc文件只有在文件被当成模块导入时才会生成,即模块加载的时候(import)。
-
也就是说,Python解释器认为,只有import进行的模块才需要被重用。
-
主文件一般只需要加载一次不会被其他模块导入,所以一般主文件不会生成pyc文件:
- 执行 python test.py 会对test.py进行编译成字节码并解释执行,但不会生成test.pyc。
- 如果test.py中加载了其他模块,如import urllib2,那么python会对urllib2.py进行编译成字节码,生成urllib2.pyc,然后对字节码解释执行。
-
-
加载模块时,如果同时存在.py和.pyc,python会使用.pyc运行:
- 在生成pyc文件的同时,写入了一个Long型的变量,用于记录最近修改的时间,如果.pyc的编译时间早于.py的时间,则重新编译.py,并更新.pyc文件。
3.2 pyc文件的生成
-
代码编译生成:
-
对于单个文件,使用方法非常简单,如下所示,xxx.py是需要编译的python源文件:
import py_compile # py_compile是Python的自带模块 py_compile.compile('xxx.py') # py_compile.compile(file[, cfile[, dfile[, doraise]]])可将.py文件编译生成.pyc文件(默认)
-
对于多个文件一般来说:
- 我们的工程都是在一个目录下的,一般不会说仅仅编译一个py文件而已,而是需要把整个文件夹下的py文件都编译为pyc文件。
- python又为了我们提供了另一个模块:compileall 。使用方法如下:
import compileall compileall.compile_dir(r'/Users/xxx/PythonFiles/')
-
-
Python Shell 命令生成:
直接通过命令来运行:
python3 -m py_compile ****.py //-m 表示把后面的模块当成脚本运行 python3 -O -m py_compile ****.py //-O 优化成字节码
-
可以看到命令中并没有用到compile()函数,这是因为py_compile模块的main()函数中调用了compile()。
-
python3 -m py_compile ****.py
其效果等效于如下代码:import py_compile py_compile.compile('****.py') # 也可以是包含.py文件的目录路径 #此处尽可能使用raw字符串,从而避免转义的麻烦。比如,这里不加“r”的话,你就得对斜杠进行转义
-
如果你想看compile(), compile_dir(), compile_path()具体每个参数是干吗用的,可以使用print py_compile.compile().__doc__来查看,或者直接打开py_compile.py,compileall.py文件来看。
3.3 pyc文件的使用
- 直接使用python编译源码时,会生成对应的(.pyc)文件,这些文件会被存放在__pycache__文件夹下。
我们可以将里边的文件copy出来,这时候可以删除对应的.py文件,同时需要将xxx.pyc文件的名字进行修改。
例如: python 的.py文件叫 main.py,生成的.pyc文件叫main.cpython-36.pyc,在我们使用该模块的时候,需要将main.cpython-36.pyc修改为main.pyc,这里的“cpython-36”表示的是我在python 3.6 的环境下编译的。
- 如果你直接从一个环境中copy到另一个环境中会出现一些bug,主要原因是编译的python版本与目前运行的python版本不同导致的。
- 编译成.pyc文件之后,尽量保证编译环境与运行环境一致。
【部分内容参考自】
- Python程序运行机制详解(入门必读):http://c.biancheng.net/view/2166.html
- 解释器和虚拟机的区别有哪些?:https://www.zhihu.com/question/59570108
- python 解释器及其工作原理:https://www.cnblogs.com/Bottle-cap/articles/10123700.html
- Python虚拟机运行原理:https://blog.csdn.net/qq_41312839/article/details/79470823
- python 编译pyc以及pyc包导入使用:https://blog.csdn.net/qq_32782339/article/details/109775099
- Python在什么时候会生成pyc文件呢?:https://www.jianshu.com/p/40a42bf1d15d
以上是关于面试python程序运行机制与pyc文件的主要内容,如果未能解决你的问题,请参考以下文章