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稍微想一想就有了。
13.参考
http://pythonhosted.org/PyInstaller/