pyinstaller教程(二)-快速使用(打包python程序为exe)

1.介绍

PyInstaller 是一个强大的 Python 打包工具,可以将 Python 程序打包成独立的可执行文件。以下会基于如何在win系统上将python程序打包为exe可执行程序为例,介绍安装方式、快速使用、注意事项以及特别用法。

2.安装方式

通过 pip 安装
PyInstaller 可以通过 pip 命令在线安装。这是最简单和推荐的安装方式。

pip install pyinstaller

这种方法适用于所有操作系统

3.快速开始

3.1 基本用法

打包一个 无任何调用及依赖的Python脚本 非常简单,只需指定作为程序入口的脚本文件即可。

pyinstaller myscript.py

这个命令执行会生成下图文件

  • 当前目录下写入 myscript.spec(与脚本名相同)
  • 在当前目录创建 build目录,并写入一些日志文件和工作文件。
  • dist如果不存在则在当前目录中创建。
  • 将可执行文件夹写入文件夹 myscriptdist,dist包含一个目录 _internal和一个文件 myscript.exe_internal目录中包含脚本所有的额外依赖包括python解释器、dll动态库等。

pyinstaller目录结构

dist/myscript

如何想打包成一个exe文件的,可指定参数 -F或者--onefile,例如,

   pyinstaller -F myscript.py 

其中 -F 参数表示生成单个可执行文件。
dist/myscript

参数(仅解释部分个人认为还算常用的参数)

参数参数描述
-F, –onefile打包一个单个文件,如果你的代码都写在一个.py文件的话,可以用这个,如果是多个.py文件就别用
-D, –onedir打包多个文件,在dist中生成很多依赖文件,适合以框架形式编写工具代码,我个人比较推荐这样,代码易于维护
-w,–windowed,–noconsole使用Windows子系统执行.当程序启动的时候不会打开命令行(只对Windows有效)
-c,–nowindowed,–console在部署时包含 TCL/TK
–icon=<FILE.ICO>file.ico添加为可执行文件的资源**(只对Windows系统有效),改变程序的图标 pyinstaller -**i ico路径 xxxxx.py
–icon=<FILE.EXE,N>file.exe的第n个图标添加为可执行文件的资源**(只对Windows系统有效)**
-n NAME, –name=NAME可选的项目**(产生的spec)名字.如果省略,第一个脚本的主文件名将作为spec**的名字

3.2 高阶用法

既然是高阶用法,那便需要更深层次的理解,并能解决一些困难问题,例如多脚本调用、复杂依赖等问题。本章节首先从spec文件讲起,基于 spec文件会讲解如何通过修改spec文件以达到一些目的。

3.2.1 spec介绍

上一节提到在执行 pyinstaller myscript.py后会生成 myscript.spec文件,该文件实际上是决定整个打包过程的配置文件,因此对于pyinstaller的高阶用法将针对spec文件展开。

首先认识一下该文件的内容:

# -*- mode: python ; coding: utf-8 -*-

# 第一步:分析入口脚本,分析所有导入以及依赖。
# 分析完后a会产生4个变量:
#     a.pure 依赖的纯 python 文件->("module", "D:\\XXX\XXX\module.py", "PYMODULE")
#     a.scripts 依次执行的脚本文件->('hook', 'D:\\XXX\\hook.py', 'PYSOURCE')
#     a.binaries 依赖的二进制文件->('python38.dll', 'D:\\XXX\\python38.dll', 'BINARY')
#     a.datas 依赖的非二进制文件->('input.txt', 'D:\\XXX\\input.txt', 'DATA'
a = Analysis(
    ['myscript.py'],	# 入口python脚本,即待分析的脚本入口
    pathex=[],		# 模块搜索的路径,默认当前环境变量
    binaries=[],	# 脚本所需的非python模块,例如DLL动态库,[ ( '/usr/lib/libiodbc.2.dylib', '.' ) ]
    datas=[],		# 脚本所需的非二进制文件,[ ( 'src/README.txt', '.' ), ( '/mygame/sfx/*.mp3', 'sfx' )]
    hiddenimports=[],	# 预先指定PyInstaller 无法自动检测到的模块
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],	# 预先需要排除的模块,即不希望打包进来的模块
    noarchive=False,
    optimize=0,
)

# 创建包含python的主程序以及依赖项,该部分代码会被打包进exe文件,在exe运行时会解压到临时文件然后被调用。
pyz = PYZ(a.pure)

# 创建exe文件
exe = EXE(
    pyz,				# 包含了纯python代码
    a.scripts,				# 包含了data以及依赖项
    [],					# 包含需要打包到 exe 文件内的二进制文件
    exclude_binaries=True,		# 默认为True,所有的二进制文件将被排除在exe之外
    name='myscript',			# exe文件命名
    debug=False,			# 打包过程是否打印调试信息
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,			# 默认为True,在控制台窗口中运行,否则作为后台进程运行
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)
# 组织收集exe的依赖
coll = COLLECT(
    exe,
    a.binaries,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='myscript', # dist目录下的目录名称
)

然后已经存在该spec文件后,可以通过执行如下命令进行打包。

pyinstaller myscript.spec

3.2.2 适用场景

(1)打包的依赖库缺少文件、存在额外的数据要拷贝
# -*- mode: python ; coding: utf-8 -*-

########################>>重点在这里<<#####################################
# 加入 打包过程中遇到numpy的依赖问题
import os
from importlib.util import find_spec
# 空列表,用于准备要复制的数据
datas = []
# 存在依赖问题的模块
manual_modules = ['numpy', 'librosa']
for m in manual_modules:
    if not find_spec(m): 
	raise Except(f"{m}模块未找到!")
    datas.append((os.path.dirname(find_spec(m).origin, m))  	# 以 (src, dst) 元组的形式添加到 datas 列表

# 额外复制的文件
my_files = ['/data/input.txt', ]
for file in my_files:
    datas.append((file, '.'))		# 将文件复制到打包目标路径的根目录
###########################################################################
a = Analysis(
    ['myscript.py'],	# 入口python脚本,即待分析的脚本入口
    pathex=[],		# 模块搜索的路径,默认当前环境变量
    binaries=[],	# 脚本所需的非python模块,例如DLL动态库,[ ( '/usr/lib/libiodbc.2.dylib', '.' ) ]
    datas=datas,	# 脚本所需的非二进制文件,[ ( 'src/README.txt', '.' ), ( '/mygame/sfx/*.mp3', 'sfx' )]
    hiddenimports=[],	# 预先指定PyInstaller 无法自动检测到的模块
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],	# 预先需要排除的模块,即不希望打包进来的模块
    noarchive=False,
    optimize=0,
)

......
(2)暴露打包后可能会修改的python脚本

可以利用a.datas的特点,将一些纯python脚本在打包时排除在exe之外,例如将核心代码 myscript.myscripyt_core.py排除在exe外,以便后期修改代码。

其原理是将保存在 a.pure中的纯python代码替换到 a.datas中。

......
a = Analysis(
    ['myscript.py'],	# 入口python脚本,即待分析的脚本入口
    pathex=[],		# 模块搜索的路径,默认当前环境变量
    binaries=[],	# 脚本所需的非python模块,例如DLL动态库,[ ( '/usr/lib/libiodbc.2.dylib', '.' ) ]
    datas=[],		# 脚本所需的非二进制文件,[ ( 'src/README.txt', '.' ), ( '/mygame/sfx/*.mp3', 'sfx' )]
    hiddenimports=[],	# 预先指定PyInstaller 无法自动检测到的模块
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],	# 预先需要排除的模块,即不希望打包进来的模块
    noarchive=False,
    optimize=0,
)
######################>>重点在这里<<###########################
# 需要暴露在外的python(不带 .py)
my_modules = ['myscript.myscript_core"]
# 将被排除的模块添加到 a.datas,同时将module排除在pure_list
pure_list = []
for mod in a.pure:
   if mod[0] in my_modules:
	mod[-1] = "DATA"
	a.datas.append(mod)
   else:
	pure_list.append(mod)
a.pure = pure_list
##############################################################

# 创建包含python的主程序以及依赖项,该部分代码会被打包进exe文件,在exe运行时会解压到临时文件然后被调用。
pyz = PYZ(a.pure)

......

4. 注意事项

在打包复杂的python工程或者项目时请注意:

  1. 务必指定入口脚本(不可使用-m 模块方式执行)。
  2. python脚本中尽量避免使用from XXX import *。
  3. 避免使用importlib来动态导入模块,避免使用。
  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值