刚开始用PyInstaller打包Python程序时,我踩过一个坑:程序在本机运行好好的,发给同事却报错找不到配置文件。折腾半天才发现,原来程序依赖的config.json根本没被打包进去。这就是--add-data参数存在的意义——它专门用来处理那些非.py的资源文件。
常见的资源文件类型包括但不限于:
这些文件有个共同特点:它们不会被PyInstaller自动编译打包。如果不做特殊处理,打包后的程序就像搬家时只带了家具没带日用品——看起来完整,实际根本没法用。
--add-data的完整语法是这样的:
bash复制pyinstaller your_script.py --add-data="源路径;目标路径"
这里有个平台差异需要特别注意:
;分隔路径:分隔路径举个实际例子,假设项目结构如下:
code复制project/
├── src/
│ └── main.py
├── config/
│ └── settings.json
└── images/
└── logo.png
要把config和images都打包进去,命令应该是:
bash复制# Windows
pyinstaller src/main.py --add-data="config/*;config" --add-data="images/*;images"
# Linux/macOS
pyinstaller src/main.py --add-data="config/*:config" --add-data="images/*:images"
我总结了几种常见场景的路径写法:
--add-data="config/settings.json;config"--add-data="images/*;images"--add-data="docs/manual/*;docs/manual"--add-data="README.md;."注意:目标路径中的
.表示可执行文件所在目录。如果写--add-data="*.txt;resources",就会把所有txt文件放到打包后的resources子目录下。
最近给客户打包一个AI工具时,遇到了这样的资源结构:
code复制ai_app/
├── main.py
├── models/
│ ├── detector.pth
│ └── classifier.h5
├── config/
│ ├── default.yaml
│ └── custom.json
├── assets/
│ ├── icons/
│ │ └── app.ico
│ └── templates/
│ └── report.html
└── docs/
└── manual.pdf
对应的打包命令相当壮观:
bash复制pyinstaller main.py \
--name "AI_Toolkit" \
--icon "assets/icons/app.ico" \
--add-data="models/*;models" \
--add-data="config/*;config" \
--add-data="assets/icons/*;assets/icons" \
--add-data="assets/templates/*;assets/templates" \
--add-data="docs/manual.pdf;docs" \
--windowed
这里分享两个实用技巧:
相对路径转绝对路径:当项目结构复杂时,建议先获取脚本所在目录:
python复制import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(BASE_DIR, 'config/settings.json')
开发/生产环境适配:可以在代码中判断是否打包运行:
python复制if getattr(sys, 'frozen', False):
# 打包后运行
base_path = sys._MEIPASS
else:
# 开发环境运行
base_path = os.path.dirname(__file__)
当参数太多时,直接修改spec文件会更方便。找到datas字段:
python复制a = Analysis(
['main.py'],
datas=[
('config/*', 'config'),
('models/*', 'models'),
('assets/icons/*', 'assets/icons')
],
...
)
每个资源用元组表示,格式(源路径, 目标路径)。多个资源之间用逗号分隔。
我习惯先用命令行生成基础spec:
bash复制pyinstaller --name MyApp main.py --add-data="temp/*;temp"
然后修改生成的spec文件,最后用spec直接打包:
bash复制pyinstaller MyApp.spec
跨平台项目最头疼的就是路径分隔符问题。我的解决方案是:
python复制import platform
is_windows = platform.system() == 'Windows'
separator = ';' if is_windows else ':'
然后在构建脚本中动态生成命令:
python复制cmd = f'pyinstaller main.py --add-data="config/*{separator}config"'
打包后访问资源要特别注意:
sys._MEIPASS获取临时解压目录os.path.join拼接路径示例代码:
python复制import sys
import os
def resource_path(relative_path):
""" 获取打包后资源的绝对路径 """
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
# 使用示例
config_file = resource_path('config/settings.json')
如果运行时提示找不到文件,建议:
--debug all参数查看详细打包过程bash复制# Windows
pyi-archive_viewer your_app.exe
修改资源文件后,必须重新打包才能生效。我开发时常用这个脚本自动重建:
bash复制#!/bin/bash
rm -rf build/ dist/
pyinstaller main.spec
对于需要动态加载的资源,比如插件或用户上传的模板,建议:
大文件会显著增加打包体积,可以考虑:
python复制from PyInstaller.utils.win32.versioninfo import SetVersion
a = Analysis(
...,
compress=True, # 启用压缩
noarchive=False
)
对于图片资源,还可以先用工具压缩:
bash复制# 使用optipng压缩PNG
optipng -o7 assets/*.png