使用PyInstaller (--onefile) 打包数据文件

181 投票
16 回答
281373 浏览
提问于 2025-04-17 03:47

我正在尝试用PyInstaller制作一个单文件的EXE文件,里面要包含一张图片和一个图标。但是我怎么也搞不定这个--onefile选项。

如果我使用--onedir,一切都运行得很好。可是一用--onefile,编译出来的EXE在运行时就找不到那些额外的文件。它能找到DLL和其他所有东西,就是找不到那两张图片。

我查看了运行EXE时生成的临时目录(比如\Temp\_MEI95642\),发现文件确实在里面。当我把EXE放到那个临时目录里时,它就能找到这些文件。这让我很困惑。

这是我在.spec文件中添加的内容

a.datas += [('images/icon.ico', 'D:\\[workspace]\\App\\src\\images\\icon.ico',  'DATA'),
('images/loaderani.gif','D:\\[workspace]\\App\\src\\images\\loaderani.gif','DATA')]     

我还要补充一下,我也试过不把它们放在子文件夹里,但没有什么区别。

编辑: 由于PyInstaller更新,我把较新的答案标记为正确。

16 个回答

56

其他所有的回答都在说,如果你的应用程序没有被打包成可执行文件(也就是sys._MEIPASS没有设置),那么就使用当前工作目录。这样做是不对的,因为它会限制你只能在脚本所在的目录运行你的应用程序。

一个更好的解决办法是:

import sys
import os

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)
65

pyinstaller会把你的数据解压到一个临时文件夹里,并把这个文件夹的路径存储在一个叫_MEIPASS2的环境变量中。为了在打包模式下获取_MEIPASS2目录,并在解压(开发)模式下使用本地目录,我使用了以下代码:

def resource_path(relative):
    return os.path.join(
        os.environ.get(
            "_MEIPASS2",
            os.path.abspath(".")
        ),
        relative
    )

输出结果:

# in development
>>> resource_path("app_icon.ico")
"/home/shish/src/my_app/app_icon.ico"

# in production
>>> resource_path("app_icon.ico")
"/tmp/_MEI34121/app_icon.ico"
252

新版本的PyInstaller不再设置env变量了,所以Shish的那个很棒的回答就不适用了。现在路径是通过sys._MEIPASS来设置的:

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

撰写回答