使用onefile选项在Pyinstaller中添加数据文件

26

我正在尝试将图像添加到由Pyinstaller生成的单个文件中。我已经阅读了许多类似于这个问题/论坛和那个问题,但仍然无法正常工作。

我知道对于单个文件操作,Pyinstller会产生一个可以通过sys.MEIPASS访问的临时文件夹。但是我不知道在我的脚本中应该在哪里添加这个sys.MEIPASS

请说明以下内容:

1- sys.MEIPASS应该在哪里以及如何添加?在Python脚本还是spec文件中?

2- 使用的确切命令是什么?我已经尝试过

pyinstaller --onefile --windowed --add-data="myImag.png;imag" myScript.py
或者
pyinstaller --onefile --windowed myScript.py

然后在规格文件中添加 ('myImag.png','imag'),然后运行

pyinstller myScript.spec

没有一个起作用。

注意:我在Windows 7下使用Python 3.6。


我认为 --add-data 是正确的,而不是 --add-date。 - Hoseong Jeon
当然,只是打字错误。但我在执行时已经改正了。不过还是谢谢。 - AhmedWas
5个回答

45

使用 PyInstaller 打包成单个文件后,运行 .exe 文件会将所有内容解压到 TEMP 目录中的一个文件夹中、运行脚本,然后丢弃临时文件。每次运行时临时文件夹的路径都会更改,但对其位置的引用被添加到 sys 中作为 sys._MEIPASS

要利用此功能,当 Python 代码读取任何也将打包进您的 .exe 文件的文件时,需要将文件位置更改为位于 sys._MEIPASS 下。换句话说,您需要在您的 Python 代码中将其添加进去。

以下是一个示例,使用您提供的链接中的代码来调整文件路径以正确地定位到打包为单个文件时的位置。

示例

# data_files/data.txt
hello
world

# myScript.py
import sys
import os

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)

def print_file(file_path):
    file_path = resource_path(file_path)
    with open(file_path) as fp:
        for line in fp:
            print(line)

if __name__ == '__main__':
    print_file('data_files/data.txt')

使用以下选项运行PyInstaller会将文件打包:

pyinstaller --onefile --add-data="data_files/data.txt;data_files" myScript.py
构建myScript.exe,它可以正确运行并打开并读取打包的数据文件。

非常感谢您!我一直在努力理解如何使用pyinstaller打包数据文件。 - Toutsos
1
感谢您提供一个可工作的最小示例!请注意,';'分隔符仅适用于Windows。对于Linux和Mac,它是':',如此处所述:https://dev59.com/J1gR5IYBdhLWcg3wAJT7#59710336 - rmharrison
我认为这是一个很好的答案,但我也认为使用这个链接作为起点会很不错,只需使用os.path来识别bundle_dir。 - undefined

5
我尝试更改我的Python脚本的工作目录,看起来已经成功了:
import os 
import sys

os.chdir(sys._MEIPASS)
os.system('included\\text.txt')

我的 PyInstaller 命令:

pyinstaller --onefile --nowindow --add-data text.txt;included winprint.py --distpath .

os.chdir(sys._MEIPASS) 运行良好。谢谢! - ZR Han

2
我使用了一个命令提示符命令而不是.spec文件。
该命令排除了shapely,然后再将其添加回来(我猜这会调用不同的导入过程)。这展示了如何添加文件夹而不仅仅是文件。
pyinstaller --clean --win-private-assemblies --onefile --exclude-module shapely --add-data C:\\Python27\\Lib\\site-packages\\shapely;.\\shapely --add-data C:\\Python27\\tcl\\tkdnd2.8;tcl main.py

1
我编写了一个简单的函数,当你将你的*.py文件作为脚本(例如在调试器或命令行中运行)时,可以从本地路径获取资源,或者当作为pyinstaller单文件可执行文件运行时,可以从临时目录获取资源。
import sys
from pathlib import Path

def fetch_resource(resource_path: Path) -> Path:
    try:  # running as *.exe; fetch resource from temp directory
        base_path = Path(sys._MEIPASS)
    except AttributeError:  # running as script; return unmodified path
        return resource_path
    else:  # return temp resource path
        return base_path.joinpath(resource_path)

这样,如果您的脚本有任何硬编码路径,您可以直接使用它们,例如: self.iconbitmap(fetch_resource(icon_path)),并且icon_path将根据环境进行适当更新。

您需要告诉pyinstaller为您希望以此方式使用的任何资产添加--add-data,例如添加整个“assets”文件夹:

pyinstaller -w -F --add-data "./src/assets/;assets"

或者您可以直接将内容添加到您的pyinstaller spec文件中的a = Analysis块中的datas列表中。

datas = ['(./src/assets)', 'assets']

0

如果要访问临时文件夹,更简单的方法是这样做:

bundle_dir = getattr(sys, '_MEIPASS', path.abspath(path.dirname(__file__)))
data_path = os.path.abspath(path.join(bundle_dir, 'data_file.dat'))

从阅读文档中获取


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接