Python 程序的打包、交付与他机验收
程序运行要靠解释器和库。打包把它们和代码一起装进可执行文件,体积与代码行数几乎无关。
程序运行时用到、但不是它自己写的东西叫依赖;依赖加上系统本身的状况合称环境。
把代码、Python 解释器和用到的全部库装进一个可执行文件,使用者不需要装 Python。
双击就能运行的程序文件,Windows 上是 .exe。
"在我电脑上跑得好好的"
对方提示找不到模块,或双击没反应
代码、解释器、库一起带上
对方什么都不用装
把代码写短
体积几乎不变,代码本来只占几千字节
只导入真正用到的库
少装一个库,就少一块体积
PyInstaller 用一行命令完成打包。参数写法随版本不同,拿到命令先查自己装的版本。
最常用的 Python 打包工具,用 pip 安装。先分析代码用到哪些库,再把它们连同解释器收进去。
跟在命令后面、以减号开头的开关,例如 --onefile。
打包过程留下的 build 文件夹和 .spec 文件,交付时不必给。
# 安装打包工具。装一次即可,之后打包任何脚本都能用 pip install pyinstaller # 查看版本号。参数写法在不同版本上略有出入,出问题时这个数字要一并告诉 AI pyinstaller --version # 第一次打包:先不加隐藏控制台的参数。打包要跑一两分钟,属正常 pyinstaller --onefile -n 借还登记器 登记器.py # 确认一切正常之后再打这一次。图标必须是真正的 .ico,把 png 改扩展名不行 pyinstaller --onefile --noconsole --icon=icon.ico -n 借还登记器 登记器.py (中间省略数十行编译过程) Building EXE from EXE-00.toc completed successfully.
网上或 AI 给的参数写法
提示"无法识别的参数"
pyinstaller --version,版本号一并告诉 AI
拿到与本机版本对得上的写法
--noconsole 隐藏黑窗口,报错也一起消失。第一次打包先留着控制台,调通后再加参数重打。
运行程序时弹出的黑色窗口,程序打印的内容和报错都显示在这里。
第一次不加 --noconsole,留着黑窗口调试;确认正常后加上它重打一次。
先加参数,再调试
出错时窗口凭空关掉,唯一的线索先被关掉了
第一次留黑窗口,第二次加参数重打
报错留在黑窗口里,改好再隐藏
报错只打印在控制台里
使用者只看到窗口凭空关掉
messagebox.showerror('出错了', …)
使用者看到中文提示和出错原因
单文件程序运行时先解压到临时目录,__file__ 指向那里。数据文件要用 app_dir() 定位,放在 exe 旁边。
单文件程序运行时先把自己解压到这里。每次运行位置不同,程序结束后即被清理。
打包后取 sys.executable 的目录,直接运行时取 __file__ 的目录,两种情况都指向程序所在的文件夹。
| 位置 | 直接运行 .py | 打包成单文件之后 |
|---|---|---|
__file__ 所在目录 | 脚本所在的文件夹 | 临时解压目录,每次运行都不同 |
sys.executable 目录 | Python 解释器所在目录 | exe 所在的文件夹 |
| 当前工作目录 | 启动命令时所在的位置 | 使用者从哪里启动就是哪里 |
sys._MEIPASS | 不存在 | 临时解压目录 |
同一台机器上实际跑出的结果。打包后另外三项都会变,只有 sys.executable 指向想要的位置。
# ① 是不是打包过的程序。打包之后 sys.frozen 才存在 frozen = getattr(sys, 'frozen', False) # ② 源文件所在目录。打包之后指向临时解压目录,不是 exe 所在的地方 print('__file__ 所在目录 :', Path(__file__).parent) # ③ 可执行文件所在目录。打包之后就是 exe 所在的那个文件夹 print('sys.executable 目录:', Path(sys.executable).parent) # ④ 当前工作目录。取决于使用者从哪里启动,与程序放在哪里无关 print('当前工作目录 :', os.getcwd()) # ⑤ 打包时临时解压到哪里。只有打包后的单文件模式才有这一项 if hasattr(sys, '_MEIPASS'): print('临时解压目录 :', sys._MEIPASS) # ⑥ 本章要记住的写法:数据文件放在程序旁边时,用这个函数定位 def app_dir(): if getattr(sys, 'frozen', False): return Path(sys.executable).parent return Path(__file__).parent
| 写法 | 作用 | 用错时 |
|---|---|---|
| getattr(sys, 'frozen', False) | 判断是不是打包过的程序;直接运行时取不到,返回默认值 False | 写成 sys.frozen,直接运行 .py 会报错 |
| Path(sys.executable).parent | 打包后就是 exe 所在的文件夹,与从哪里启动无关 | 直接运行时指向 Python 解释器的安装目录,不能一律用它 |
| os.getcwd() | 当前工作目录,随启动方式变化 | 用它定位数据文件,换一种启动方式就找不到库 |
| def app_dir(): … | 把两种情况合成一个函数,定位数据文件一律调用它 | 各处分别判断,改一次要改好几处,漏掉一处就出问题 |
DB = Path(__file__).parent / 'items.db'
每次都提示写入成功,下次打开记录不见了
DB = app_dir() / 'items.db'
items.db 出现在 exe 旁边,关掉重开还在
在自己机器上跑 .py
路径问题永远不会出现
操作一次,看 exe 旁边有没有数据文件
问题在打包之后当场暴露
打包程序常被杀毒软件误报,被拦下不等于程序有问题。处理办法写在 README 最前面。
正常程序被杀毒软件当成未知程序或可疑程序拦下。
由第三方机构颁发、用于证明程序来源的凭证。
直接把 exe 交给不熟悉的人
对方看到安全警告,大概率直接删掉
"部分杀毒软件会拦截,加入信任列表即可"
对方照着处理,程序正常使用
交付物有三样:可执行文件、数据文件、README。少一样,对方就会卡在某一步。
交给别人、让别人能接着用下去的全部东西:可执行文件、数据文件、README。
随程序一起交付的说明,写给拿到成品的人看。
| README 的部分 | 写什么 | 样例中的写法 |
|---|---|---|
| 这是什么 | 程序的用途 | 用来登记社团物资的借出与归还,随时查看还有哪些东西没有还回来。 |
| 怎么用 | 操作步骤;写清点哪里、看到什么算成功 | 双击 借还登记器.exe。借出:填写物资名称与借用人,点「登记借出」。 |
| 运行环境 | 系统要求;不写安装 Python 的步骤 | Windows 10 或 Windows 11,64 位。不需要安装 Python,也不需要安装任何其他软件。 |
| 数据存在哪里 | 数据文件的位置、拷贝和备份 | items.db 就在 exe 旁边……备份时只需备份 items.db。 |
| 可能遇到的情况 | 已知问题和处理办法,误报放在最前 | 杀毒软件提示未知程序或可疑行为:在杀毒软件中把它加入信任列表即可。 |
读者是拿到成品、不懂技术的人,不要出现"依赖""部署""环境变量"这类术语。
AI 建议用参数把 items.db 一起打包进去
数据变成只读,登记的记录下次打开就没了
items.db 与 exe 放在同一个文件夹
记录留在 items.db 里,备份也只备它
只发 exe 和数据文件
对方的第一句话是"这个怎么用"
五部分写全,放进交付包
对方照着 README 独立完成操作
自己机器上能跑,不算数。发布前在没装过 Python 的电脑上,从双击开始完整走一遍。
在一台没有装过 Python 的电脑上,只用交付的三样东西,从双击开始完整运行一遍。
给这次发布的东西编的号,如 v1.0、v1.1;窗口标题、README、文件名三处写同一个号。
把这一版正式交出去,此后不再私自改动。
本机早已装好 Python 和库
换一台电脑就出状况
三样一起拷过去,全程不作口头说明
卡在哪一步,就知道还缺什么
人能读,程序读不懂
哪个更新说不清
窗口标题与 README 写同一个号
对方报问题时先对版本号,一问就对得上
打包结果取决于本机环境。提问时把系统、版本、库说全,把已知风险写成问句。
| 提示词里的要求 | 为什么要写 | 对应小节 |
|---|---|---|
| 写明系统、Python 版本、用到哪些库 | 打包对环境敏感,信息不全时 AI 只能按最常见的情形作答 | 14.1、14.2 |
| 打包之后"同目录"会不会变,代码该怎么写 | 本章最不容易发现的问题,AI 通常不会主动提 | 14.4 |
| 双击时不出现黑色控制台窗口 | 加之前先确认程序已经能用弹窗提示错误 | 14.3 |
| 逐个参数说明是做什么用的 | 参数在不同版本上写法有出入,看懂了才能自己调 | 14.2 |
报错时附上 pyinstaller --version 的结果 | 同一段报错在不同版本上原因可能完全不同 | 14.2 |
| README 里不要出现依赖、部署这类术语 | 读者是拿到成品的人,不懂技术 | 14.6 |
提示词里不提数据文件的位置
AI 给的代码直接运行正常,打包后数据悄悄写进临时目录
"打包之后,这个同目录会不会变?"
AI 给出打包前后都能定位的写法
额外的打包工具、虚拟环境管理器等
装得越多,出问题的地方越多
其余工具本章一律不需要
这些工具在正式项目里各有用处,本章用不到
成品要在没装过 Python 的电脑上独立运行,并读写自己的数据文件。
先在本机跑一遍,把功能问题解决干净。带界面的作品,出错时要弹窗提示,不能只在黑窗口里报错
产出:一个可用的脚本数据文件改用 app_dir() 定位。改完先按 .py 运行一次,确认数据仍然存在原来的位置
产出:改过路径的脚本打包后在自己机器上完整操作一遍,确认数据文件出现在 exe 旁边。这一条不过,后面都不用做
产出:可执行文件与运行截图五部分齐全。写完自己照着读一遍,自己都答不上来的地方,回去改程序或改说明
产出:一份 README三样东西拷到验收机,从双击开始,只照 README 操作,全程不作口头说明。卡在哪一步,就说明 README 缺了什么
产出:他机运行截图只给同组同学三样东西,不在旁边指导。他卡在哪一步、哪句说明看不懂,如实记下;写清你踩到并解决的那个打包问题
产出:互测记录与问题说明打包出问题时,按现象查表。报错原文和版本号一起发给 AI。
| 现象 | 原因 | 处理 |
|---|---|---|
| pyinstaller 不是内部或外部命令 | 工具没装,或者装到了另一个 Python 环境 | 运行 pip install pyinstaller;仍不行时改用 python -m PyInstaller |
| 提示无法识别的参数 | 参数写法与本机的工具版本对不上 | 运行 pyinstaller --version,把版本号连同报错一起发给 AI |
| 双击后闪一下就没了 | 程序运行出错,控制台随即关闭 | 在终端里运行这个 exe,报错会留在屏幕上;或去掉 --noconsole 重打一次 |
| 程序能开,但数据每次都不见了 | 数据写进了临时解压目录 | 改用 app_dir() 定位数据文件,重新打包 |
| 杀毒软件直接把成品删了 | 没有代码签名,被误报 | 在杀毒软件中加入信任列表,并把这一条写进 README |
| --icon 报错说图标无效 | 用了 png 直接改扩展名,不是真正的 ico | 用图标转换工具生成正规的 .ico 文件再指定 |
| 打包过程中提示找不到某个模块 | 工具没有分析出这个库被用到了 | 把完整输出发给 AI,通常用 --hidden-import 显式指定即可 |
| 在别人电脑上提示没有权限 | 程序放在只读位置,无法在旁边写数据 | 改放到桌面或文档目录;README 里提前写明这一条 |
本次上机的成果就是作业 4:提交四项内容,按七条标准验收;选做一项。
| 作业 4 评分构成 | 占比 |
|---|---|
| 能在他机独立运行 | 40% |
| 功能完整可用 | 35% |
| 交付物齐整 | 25% |
按教师提供的作业模板填写,文档开头注明所选题目。提交渠道见课堂通知,当堂提交。
零代码应用开发 · 第 14 章 打包成软件