pyinstaller打包后需用getattr(sys, '_meipass', os.path.dirname(os.path.abspath(__file__)))获取资源根路径,开发时回退到源码目录,打包后指向临时解压目录;所有资源路径必须基于此动态计算,不可依赖__file__或裸相对路径。

PyInstaller 打包后 __file__ 和相对路径会失效,这是必然的——它根本不是在源码目录下运行,而是在临时解压目录(如 ./_MEIxxxxxx/)里执行。直接用 os.path.dirname(__file__) 拿到的路径指向的是那个临时目录,不是你期望的资源位置。
怎么拿到真正的打包后资源根目录?
必须区分运行环境:开发时走源码路径,打包后走 PyInstaller 提供的 sys._MEIPASS。不能硬编码或只依赖 __file__。
- 用
getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))作为基础路径 - 所有资源(图片、配置、数据文件)都基于这个基础路径拼接,比如:
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))) config_path = os.path.join(base_path, 'config.json')
- 如果用了
pathlib,写法等价:from pathlib import Path base_path = Path(getattr(sys, '_MEIPASS', Path(__file__).parent)) config_path = base_path / 'config.json'
为什么加了 --add-data 还是找不到文件?
因为 --add-data 只负责把文件复制进打包体,并不自动修改你的代码逻辑。路径还是得你自己算对;而且 Windows 和 Linux/macOS 的分隔符、参数格式不同,容易漏掉细节。
- Windows 下
--add-data "src;dest"(分号),Linux/macOS 是--add-data "src:dest"(冒号) -
dest是相对于sys._MEIPASS的子路径,不是程序启动目录 - 如果你把文件加到了
--add-data "assets;assets",那代码里就得用os.path.join(base_path, 'assets', 'icon.png') - 调试时可临时打印
sys._MEIPASS看实际解压位置,确认文件是否真在那里
读取 config.ini 或 JSON 时路径出错的典型表现
常见报错是 FileNotFoundError: [Errno 2] No such file or directory: 'config.json',或者读出来是空内容——其实文件存在,但路径拼错了,打开了别的地方的同名文件(比如当前工作目录下的旧配置)。
- 绝对不要用
open('config.json')这种裸路径,它依赖当前工作目录(os.getcwd()),而该目录在双击运行 exe 时可能是桌面或用户家目录 - 避免用
os.chdir()切工作目录来“修复”,这会影响其他模块行为(比如日志写入、子进程调用) - 推荐封装一个资源定位函数,统一处理:
def resource_path(relative_path): return os.path.join(getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))), relative_path)然后所有资源都走resource_path('data/users.db')
最麻烦的其实是嵌套层级深的项目结构,比如资源放在 src/myapp/assets/,但打包时你只加了 src/myapp/assets 到 _MEIPASS,结果代码里还按源码结构去 ../assets 回溯——这种路径计算在打包后完全不可靠。老老实实用 sys._MEIPASS 当唯一可信起点,别猜。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











