1.Linux系统下安装pyinstaller
# easy_install pyinstaller Adding PyInstaller 2.1 to easy-install.pth file Installing pyinstaller script to /usr/local/bin Installing pyi-grab_version script to /usr/local/bin Installing pyi-archive_viewer script to /usr/local/bin Installing pyi-build script to /usr/local/bin Installing pyi-make_comserver script to /usr/local/bin Installing pyi-bindepend script to /usr/local/bin Installing pyi-set_version script to /usr/local/bin Installing pyi-makespec script to /usr/local/bin
2.pyinstaller工具安装在/usr/local/bin下
# which pyinstaller /usr/local/bin # ls /usr/local/bin|grep pyi* pyi-archive_viewer pyi-bindepend pyi-build pyi-grab_version pyi-make_comserver pyi-makespec pyinstaller pyi-set_version
3.创建测试程序main.py
print('hello world!')
4.使用pyinstaller创建可执行程序
# pyinstaller main.py
执行后生成相关目录和文件
# tree . . ├── build │ └── main │ ├── logdict2.7.8.final.0-1.log │ ├── main │ ├── out00-Analysis.toc │ ├── out00-COLLECT.toc │ ├── out00-EXE.toc │ ├── out00-PKG.pkg │ ├── out00-PKG.toc │ ├── out00-PYZ.pyz │ ├── out00-PYZ.toc │ └── warnmain.txt ├── dist │ └── main │ ├── audioop.so │ ├── bz2.so │ ├── _codecs_cn.so │ ├── _codecs_hk.so │ ├── _codecs_iso2022.so │ ├── _codecs_jp.so │ ├── _codecs_kr.so │ ├── _codecs_tw.so │ ├── _hashlib.so │ ├── libbz2.so.1.0 │ ├── libcrypto.so.1.0.0 │ ├── libpython2.7.so.1.0 │ ├── libreadline.so.6 │ ├── libssl.so.1.0.0 │ ├── libtinfo.so.5 │ ├── libz.so.1 │ ├── main │ ├── _multibytecodec.so │ ├── readline.so │ ├── _ssl.so │ └── termios.so ├── main.py └── main.spec 4 directories, 33 files
生成了main.spec文件和build、dist两个文件夹,可执行程序位于目录./dist/main/,而该目录下所有程序均为运行时所需文件。
5.运行程序
# ./dist/main/main hello world!
6.生成单个文件
清除main.py以外文件
# rm -rf ./build ./dist main.spec生成单一执行文件# pyinstaller -F main.py执行后生成相关目录和文件# tree . . ├── build │ └── main │ ├── logdict2.7.8.final.0-1.log │ ├── out00-Analysis.toc │ ├── out00-EXE.toc │ ├── out00-PKG.pkg │ ├── out00-PKG.toc │ ├── out00-PYZ.pyz │ ├── out00-PYZ.toc │ └── warnmain.txt ├── dist │ └── main ├── main.py └── main.spec 3 directories, 11 files
7.运行程序
# ./dist/main/main hello world!
8.压缩程序
UPX使用一种叫做UCL的压缩算法,这是一个对有部分专有算法的NRV(Not Really Vanished)算法的一个开源实现。
debian下安装UPX方法如下:# apt-get install upx-ucl
确认upx路径
# which upx /usr/bin/upx
使用压缩方式打包单个可执行程序。由于–upx参数已废弃,需要使用–upx-dir参数
# pyinstaller -F --upx-dir=/usr/bin main.py # ll dist/main -rwxr-xr-x 1 albert albert 4229368 Dec 5 20:54 dist/main
对比未压缩时的体积
# pyinstaller -F --noupx main.py # ll dist/main -rwxr-xr-x 1 albert albert 4306465 Dec 5 21:03 dist/main
当然pyinstaller默认会搜索PATH路径的,所以apt安装upx的话,pyinstaller能识别出来,并自动启用压缩。
24 INFO: UPX is available.
不想压缩可以使用–noupx参数。
9.资源文件(生成文件夹模式)
当使用图片或者外部数据时,就需要把这些资源文件一同打包到程序中。还记得main.py同目录下生成的main.spec文件吗?这个文件就是配置文件。现看下需要外部资源的python程序
# cat main.py with open('foo') as fi: print(data) # cat foo Hello World!
我们为了添加资源需要使用pyi-makespec生成配置文件,然后使用pyi-build生成文件夹模式的可执行程序。
# pyi-makespec main.py wrote /home/albert/PycharmProjects/createonefile/main.spec now run pyinstaller.py to build the executable
现在看下spec配置文件的内容,万幸是个python格式的文件。
# cat main.spec # -*- mode: python -*- a = Analysis(['main.py'], pathex=['/home/albert/PycharmProjects/createonefile'], hiddenimports=[], hookspath=None, runtime_hooks=None) pyz = PYZ(a.pure) exe = EXE(pyz, a.scripts, exclude_binaries=True, name='main', debug=False, strip=None, upx=True, console=True ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=None, upx=True, name='main')
需要添加资源需要使用TOC(Table of Contents)格式(name,path,typecode)
name:打包到程序文件夹内的资源文件名path:打包前资源文件的绝对路径(包括文件名)typecode:有3种typecode description name path 'BINARY' A shared library. Run-time name. Full path name in build. 'DATA' Arbitrary files. Run-time name. Full path name in build. 'OPTION' A Python run-time option. Option code ignored.
那么需要添加spec的TOC便是
(‘foo’, ‘/home/albert/PycharmProjects/createonefile/foo’, ‘DATA’)该TOC需要放到list中,然后作为参数传递给COLLECT函数,修改后的spec文件如下:# cat main.spec # -*- mode: python -*- a = Analysis(['main.py'], pathex=['/home/albert/PycharmProjects/createonefile'], hiddenimports=[], hookspath=None, runtime_hooks=None) pyz = PYZ(a.pure) exe = EXE(pyz, a.scripts, exclude_binaries=True, name='main', debug=False, strip=None, upx=True, console=True ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=None, upx=True, name='main')
最后一步就是执行pyi-build编译可执行文件
# pyi-build main.spec # tree . . ├── build │ └── main │ ├── logdict2.7.8.final.0-1.log │ ├── main │ ├── out00-Analysis.toc │ ├── out00-COLLECT.toc │ ├── out00-EXE.toc │ ├── out00-PKG.pkg │ ├── out00-PKG.toc │ ├── out00-PYZ.pyz │ ├── out00-PYZ.toc │ └── warnmain.txt ├── dist │ └── main │ ├── audioop.so │ ├── bz2.so │ ├── _codecs_cn.so │ ├── _codecs_hk.so │ ├── _codecs_iso2022.so │ ├── _codecs_jp.so │ ├── _codecs_kr.so │ ├── _codecs_tw.so │ ├── foo │ ├── _hashlib.so │ ├── libbz2.so.1.0 │ ├── libcrypto.so.1.0.0 │ ├── libpython2.7.so.1.0 │ ├── libreadline.so.6 │ ├── libssl.so.1.0.0 │ ├── libtinfo.so.5 │ ├── libz.so.1 │ ├── main │ ├── _multibytecodec.so │ ├── readline.so │ ├── _ssl.so │ └── termios.so ├── foo ├── main.py └── main.spec 4 directories, 35 files
大家已经可以看到./dist/main/foo了吧。为了避免读取原文件,我们进入到./dist/main执行下main。
# cd ./dist/main # ./main Hello World!
10.资源文件(生成单文件模式)
单文件模式包含外部资源就要麻烦许多了,运行程序的时候先将文件解压到sys._MEIPASS指向的目录下,所以调用资源文件就需要添加os.path.join(sys._MEIPASS,filename)。但打包前调试时sys又没有_MEIPASS属性,那就又要添加如下代码
if getattr(sys, 'frozen', False): # we are running in a |PyInstaller| bundle basedir = sys._MEIPASS else: # we are running in a normal Python environment basedir = os.path.dirname(__file__)
这样调用资源文件时就使用os.path.join(basedir,filename)就可以了。我们先来看看修改后的main.py
# cat main.py import sys import os if getattr(sys, 'frozen', False): # we are running in a |PyInstaller| bundle basedir = sys._MEIPASS else: # we are running in a normal Python environment basedir = os.path.dirname(__file__) with open(os.path.join(basedir,'foo')) as fi: print(fi.read())
然后生成spec文件,onefile模式下生成的spec与onedic模式是不一样的。
# pyi-makespec -F main.py # -*- mode: python -*- a = Analysis(['main.py'], pathex=['/home/albert/PycharmProjects/createonefile'], hiddenimports=[], hookspath=None, runtime_hooks=None) pyz = PYZ(a.pure) exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, name='main', debug=False, strip=None, upx=True, console=True )
需要把资源文件整理为TOC格式,添加到spec中,不同的是onedic模式是添加到COLLECT的参数中,onefile模式并不调用COLLECT函数,所以TOC是添加到EXE参数中。修改后的spec如下
# cat main.spec # -*- mode: python -*- a = Analysis(['main.py'], pathex=['/home/albert/PycharmProjects/createonefile'], hiddenimports=[], hookspath=None, runtime_hooks=None) pyz = PYZ(a.pure) exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [('foo', '/home/albert/PycharmProjects/createonefile/foo', 'DATA')], name='main', debug=False, strip=None, upx=True, console=True )
然后编译测试
# pyi-build main.spec # cd dist # ./main Hello World!
11.多资源文件(生成单文件模式)
多资源文件时,可以把多个TOC文件放到同一个列表中
[('foo1', '/home/albert/PycharmProjects/createonefile/resource/foo1', 'DATA'), ('foo2', '/home/albert/PycharmProjects/createonefile/resource/foo2', 'DATA'), ('foo3', '/home/albert/PycharmProjects/createonefile/resource/foo3', 'DATA')]
也可以TOC的升级版Tree。Tree可以把一个目录下的文件自动生成TCO列表。
Tree(root, prefix=run-time-folder, excludes=match)root是需要打包的资源文件夹,可以是绝对路径也可以是相对路径prefix是运行时的文件夹名。代码中就需要使用os.path.join(basedir,prefixdir,’foo1′)excludes是个列表,可有可无,用于排除不需要的文件或子文件夹。既可以是文件名、文件夹名,也可以使用通配符如*.ext上边的TOC列表就可以这样表达Tree(‘/home/albert/PycharmProjects/createonefile/resource’,’resource’)代码中可这样调用basedir=os.path.join(basedir,'resource') foo1=open(os.path.join(basedir,'foo1')) foo2=open(os.path.join(basedir,'foo2')) foo3=open(os.path.join(basedir,'foo3'))
完整代码:
# tree . . ├── main.py └── resource ├── foo1 ├── foo2 └── foo3 1 directory, 4 files # cat main.py import sys import os if getattr(sys, 'frozen', False): # we are running in a |PyInstaller| bundle basedir = sys._MEIPASS else: # we are running in a normal Python environment basedir = os.path.dirname(__file__) resourcedir=os.path.join(basedir,'resource') for filename in ['foo1','foo2','foo3']: with open(os.path.join(resourcedir,filename)) as fi: print(fi.read()) # pyi-makespec -F main.py # emacs -nw main.spec ***add Tree Object to main.spec file*** # cat main.spec # -*- mode: python -*- a = Analysis(['main.py'], pathex=['/home/albert/PycharmProjects/createonefile'], hiddenimports=[], hookspath=None, runtime_hooks=None) pyz = PYZ(a.pure) exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, Tree('./resource','resource'), name='main', debug=False, strip=None, upx=True, console=True )
运行生成的文件。
# cd dist # ./main Hello foo1! Hello foo2! Hello foo3!
12.多资源文件(生成单文件夹模式)
真懒的写了,手动copy进去不就结了嘛。不嫌麻烦就参考9、10、11稍微想一想就有了。