Python的几种文件类型
Python有以下几种类型的文件:
- py:Python控制台程序的源代码文件
- pyw:Python带用户界面的源代码文件
- pyx:Python包源文件
- pyc:Python字节码文件
- pyo:Python优化后的字节码文件
- pyd:Python的库文件(Python版DLL)、在Linux上是so文件
pyc和pyo的生成方法
pyc的作用是用来跨平台使用的,和Java中的Class文件类似。pyc文件是一种字节码文件,可以加快Python解释器的加载速度,当然也可以用来做简单的防源码泄露保护。
pyo则是优化过后的字节码文件,不过pyo更像编译型语言里的中间文件。
我们可以通过Python提供的py_compile模块来进行源代码的编译。
py_compile模块只提供3个方法,分别是有关编译异常PyCompileError、有关编译compile、有关程序入口main
我们要用到的是compile方法,compile原形如下:
compile(file, cfile=None, dfile=None, doraise=False, optimize=-1)
有5个参数:
- file:必选参数,要编译的源文件
- cfile:编译后的文件,默认在源文件目录下的__pycache__/源文件名.解释器类型-python版本.字节码类型 ###例如:__pycache__/abc.cpython-34.pyo
- dfile:错误消息文件,默认和cfile一样
- doraise:是否开启异常处理,默认False
- optimize:优化字节码级别
这里分为4个等级,文档中是这样写的:
optimize级别
param optimize: The optimization level for the compiler. Valid values are -1, 0, 1 and 2. A value of -1 means to use the optimization level of the current interpreter, as given by -O command line options.
optimize为1时,优化字节码级别为最高
-1和0:设置pyc优化级别
1和2:设置pyo优化级别
数字越小,优化级别越高
准备源文件a.py和b.py,内容相同,就是一句print("python")代码
编写编译脚本:
import py_compile
py_compile.compile(file = "a.py",cfile = "a.pyc",optimize=-1)
py_compile.compile(file = "b.py",cfile = "b.pyo",optimize=1)
编译脚本代码
运行后可以看到已经成功编译成字节码文件了,分别为a.pyc和b.pyo
用文本编辑器打开a.pyc和b.pyo,如图
打开字节码文件,尝试运行这2个字节码文件。
运行效果
可见,字节码文件成功运行。
也可以直接通过Python加载模块来运行:
#编译成pyc
python -m py_compile 源代码
#编译成pyo
python -O -m py_compile 源代码
这确实可以简单地保护我们的代码,同时似乎看起来像是加密的效果,但是要注意,这不是加密,只是把源码变成优化后的字节码而已,如果想要获得源码,我们一样可以通过逆向编译来得到源码,目前有专门逆向Python字节码的工具存在。
如果需要编译整个目录内的所有源代码,请参考Python compileall
pyd可以让我们的代码更安全
如果真的想要保护代码,为何不考虑把它变成python扩展模块?(目前还没有pyd被反编译的消息)
pyd是Python中的扩展模块,相当于windows的dll,不同的是pyd只供python调用而已。
实际上,大部分的包、小模块都是以pyd形式发布的。
如果特别感兴趣的小伙伴可以深入研究下setuptools和distutils
在把源代码转换成功pyd之前,我们需要用到Cython包。
pip list | findstr "Cython"
检查Cython包
检查是否安装了Cython,没有请pip install Cython安装即可
编译pyd步骤1:生成C代码
import Cython.Build
#导入Build模块
Cython.Build.cythonize("a.py")
#a.py转换成C代码
cythonize运行完成之后,无异常的情况下会在a.py的目录下创建一个a.c文件,同时会返回一个distutils.extension.Extension对象列表
一定要注意的是:如果在Python Shell测试,一定要用绝对路径,否则会ValueError异常,cythonize不会从sys.path中读路径。
编译pyd步骤2:利用distutils生成pyd扩展模块
此时我们可以用distutils包来编译成我们要的pyd模块
编译a.py成pyd
import Cython.Build
import distutils.core
a = Cython.Build.cythonize("a.py")
#返回distutils.extension.Extension对象列表
distutils.core.setup(
name = 'pyd的编译',#包名称
version = "1.0",#包版本号
ext_modules= a,#扩展模块
author = "百家号——斌哥说Python",#作者
author_email='[email protected]'#作者邮箱
)
代码
python 执行编译的脚本 build
或
python 执行编译的脚本 build_ext
执行编译脚本编译
此时会在编译脚本所在目录生成一个build目录,里面存着C语言的.def文件和.o文件,还有我们要的pyd文件
生成的C文件
生成的pyd文件
批量编译pyd文件的误区
此时我们已经生成了1个pyd文件,如果我们是扩展包/模块的开发者,怎么批量编译呢?
总有人会犯错,例如以下2个例子:
a = Cython.Build.cythonize("a.py")
b = Cython.Build.cythonize("b.py")
distutils.core.setup(
...,
ext_modules= [a,b]
)
这样做吗?NO......
a = Cython.Build.cythonize("a.py")
a.append(Cython.Build.cythonize("b.py"))
distutils.core.setup(
...,
ext_modules= a
)
还是这样?
犯这样的错原因却是因为:
a = Cython.Build.cythonize("a.py")
type(a)
提示<class 'list'>
没错,Cython.Build.cythonize返回的是一个列表,里面有只有1个distutils.extension.Extension对象
报错如下:
异常信息
需要1个Extension或者是2个元组
批量编译pyd
方法1:提取我们要的Extension对象
import Cython.Build
import distutils.core
a = Cython.Build.cythonize("a.py")[0] #提取Extension对象
b = Cython.Build.cythonize("b.py")[0]
distutils.core.setup(
name = 'pyd的编译', #包名称
version = "1.0", #包版本号
ext_modules= [a,b], #被扩展的模块
author = "百家号——斌哥说Python", #作者
author_email='[email protected]' #作者邮箱
)
方法2:转换成C代码后再进行Extension对象实例化
import Cython.Build
import distutils.core
Cython.Build.cythonize("a.py")
Cython.Build.cythonize("b.py")
distutils.core.setup(
name = 'pyd的编译', #包名称
version = "1.0", #包版本号
ext_modules= [distutils.core.Extension('a',["a.c"]),distutils.core.Extension('b', ['b.c'])], #被扩展的模块
#[
#distutils.core.Extension('a',["a.c"]),
#distutils.core.Extension('b', ['b.c'])
#]
author = "百家号——斌哥说Python", #作者
author_email='[email protected]' #作者邮箱
)
pyc和pyo相对而言安全性较低,pyd是目前解决Python开发中代码安全性最优的一个方案。
但是要注意一点:无论是pyc还是pyo、pyd,都是跟着Python版本走的,不要指望Python2.7的东西在Python3上完美运行。
PS:如果遇到running build...提示,删掉build目录重新编译即可。