PyInstaller是一款将python脚本打包成exe可运行文件的工具,支持linux/Windows/MacOS等多个系统。
版本
PyInstaller 3.2.1 + Python 3.5.2 + Windows10 64bit,用 pyinstaller -v
查看版本
PyInstaller 安装
pip install pyinstaller
hello world
尝试打包 hello_world.py,文件内容:1
print('Hello World!')
用命令1
2cd path\to\hello_world.py
pyinstaller hello_world.py
生成dist\hello_world
目录,其中包含可执行文件hello_world.exe。可用--onefile
参数:pyinstaller --onefile hello_world.py
,将所有文件压缩在exe中。
注:如果报错UnicodeDecodeError
,根据这篇blog的建议,在cmd中执行命令chcp 65001
PyInstaller打包数据,记soundflie库
PyInstaller会自动打包import
语句引用的包,其实只包括包下的python脚本。但如果脚本依赖于数据文件或dll链接库等,PyInstaller无法自动分析出这些依赖,需要配置参数。
用tmp.py生成exe。tmp.py内容:1
2
3import soundfile # version: 0.9.0.post1
sample, sr = soundfile.read('sample.wav') # 读当前目录下的音频sample.wav
print(sample.shape)
生成exe后运行,报错找不到libsndfile64bit.dll
,因为soundfile依赖这个库,而PyInstaller并没有打包。
- 那么,首先告诉PyInstaller打包:
参考PyInstaller手册关于使用spec文件,执行命令pyi-makespec tmp.py
(同样可加上--onefile
参数),生成tmp.spec
配置文件; - 在spec文件中,向
Analysis
构造器传入参数datas
列表,添加二元组('X:\\path\\to\\site-packages\\_soundfile_data\\libsndfile64bit.dll', '_soundfile_data')
,元组第一个元素指示dll文件路径,后者指示运行时存放的目录。在上一步如果指定了--onefile
,dll文件会在运行时被解压到一个临时目录(以_MEI
开头,见下个步骤)的_soundfile_data
目录下,如果未指定,会被复制到exe同目录的_soundfile_data
目录下; - 修改
soundfile.py
源码如下,最后执行命令pyinstaller tmp.spec
1
2
3
4
5
6
7
8
9# 原始代码
_snd = _ffi.dlopen(_os.path.join(_os.path.dirname(_os.path.abspath(__file__)), '_soundfile_data', _libname))
# 修改
try:
# with --onefile, PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = _os.path.dirname(_os.path.abspath(__file__))
_snd = _ffi.dlopen(_os.path.join(base_path, '_soundfile_data', _libname))
记librosa库
librosa依赖的resampy库需要打包site-packages\resampy\data
下的两个数据文件,与对soundfile的处理一样,向datas
添加元组('X:\\path\\to\\site-packages\\resampy\\data', 'data')
,然后修改resampy包下filters.py源码:1
2
3
4
5
6
7
8
9
10
11
12
13# librosa version: 0.5.1
# resampy version: 0.1.5
# 原始代码
fname = os.path.join('data',.path.extsep.join([filter_name, 'npz']))
data = np.load(pkg_resources.resource_filename(__name__, fname))
# 修改:
try:
base_path = sys._MEIPASS
except Exception:
base_path = '.'
fname = os.path.join(base_path,'data', os.path.extsep.join([filter_name, 'npz']))
data = np.load(pkg_resources.resource_filename(__name__, fname))
此外,有几个包或模块没有被PyInstaller打包,向spec文件Analysis构造器传入参数 hiddenimports=['cython', 'sklearn.neighbors.typedefs']