使用PyInstaller捆绑数据文件(--onefile)

arb*_*ark 88 python pyinstaller

我正在尝试使用PyInstaller构建一个单文件EXE,它包含一个图像和一个图标.我无法为我的生活而努力--onefile.

如果我这样做--onedir,一切工作都很顺利.当我使用时--onefile,它找不到引用的附加文件(运行编译的EXE时).它找到了DLL和其他一切都很好,只是不是两个图像.

我查看了运行EXE时生成的temp-dir(\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')]     
Run Code Online (Sandbox Code Playgroud)

我应该补充一点,我也试过不把它们放在子文件夹中,没有什么区别.

编辑: 由于PyInstaller更新,标记的较新答案正确.

小智 125

较新版本的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)
Run Code Online (Sandbox Code Playgroud)

  • 我正在使用带有python 2.7的pyinstaller 2,而我在envs中没有`_MEIPASS2`,但是`sys._MEIPASS`效果很好,所以+1.我建议:`path = getattr(sys,'_ MEIPASS',os.getcwd())` (8认同)
  • 我建议捕获`AttributeError`而不是更通用的`Exception`.我遇到了一些问题,我错过了sys导入,并且路径默默地默认为`os.path.abspath`. (6认同)
  • 谁能告诉我们如何使用这个? (6认同)
  • 这段代码该放在哪里?这个函数需要调用吗?或者足以将其放在主文件中的任何位置? (4认同)
  • +1。感谢那。现在尝试使用 _MEIPASS2 环境变量一段时间,显然我在它仍然是环境变量时编写了我的代码,因为我记得它可以工作。突然间,当我最近重新编译时,它停止了。 (2认同)
  • 在未设置 _MEIPASS 的情况下使用当前工作目录是错误的。见[我的回答](/sf/answers/3104705201/)。 (2认同)

Shi*_*ish 50

pyinstaller将您的数据解压缩到临时文件夹中,并将此目录路径存储在_MEIPASS2环境变量中.要_MEIPASS2以打包模式获取目录并在解压缩(开发)模式下使用本地目录,我使用:

def resource_path(relative):
    return os.path.join(
        os.environ.get(
            "_MEIPASS2",
            os.path.abspath(".")
        ),
        relative
    )
Run Code Online (Sandbox Code Playgroud)

输出:

# 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"
Run Code Online (Sandbox Code Playgroud)

  • 如何,何时以及在何处使用它? (8认同)
  • 您应该在尝试使用PyInstaller编译的.py文件中使用此脚本.不要将此代码段放在.spec文件中,这不起作用.通过用`resource_path("file_to_be_accessed.mp3")替换你通常输入的路径来访问你的文件.要小心你应该使用当前版本的PyInstaller的max'回答. (4认同)

Jon*_*art 18

在应用程序不是PyInstalled(即未设置)的情况下,所有其他答案都使用当前工作目录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)
Run Code Online (Sandbox Code Playgroud)


小智 11

使用Max的出色答案和这篇关于添加额外数据文件(如图像或声音)的文章以及我自己的研究/测试,我已经找到了我认为添加此类文件的最简单方法。

如果您想查看实际示例,我的存储库位于GitHub上。

注意:这是为了使用pyinstaller 的--onefile-F命令进行编译。

我的环境如下。


分 2 步解决问题

为了解决这个问题,我们需要明确告诉 Pyinstaller 我们有额外的文件需要与应用程序“捆绑”。

我们还需要使用“相对”路径,以便应用程序在作为 Python 脚本或 Frozen EXE 运行时可以正常运行。

话虽如此,我们需要一个允许我们拥有相对路径的函数。使用Max Posted的函数我们可以轻松解决相对路径问题。

def img_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)
Run Code Online (Sandbox Code Playgroud)

我们将像这样使用上面的函数,以便当应用程序作为脚本或 Frozen EXE 运行时应用程序图标会显示。

icon_path = img_resource_path("app/img/app_icon.ico")
root.wm_iconbitmap(icon_path)

Run Code Online (Sandbox Code Playgroud)

下一步是我们需要指示 Pyinstaller 在编译时在哪里找到额外的文件,以便当应用程序运行时,它们会在临时目录中创建。

我们可以通过文档中所示的两种方式解决此问题,但我个人更喜欢管理自己的 .spec 文件,因此我们将这样做。

首先,您必须已经有一个 .spec 文件。就我而言,我能够通过使用额外参数运行来创建我需要的内容,您可以在此处pyinstaller找到额外参数。因此,我的规范文件可能与您的略有不同,但在解释重要部分后,我将发布所有内容以供参考。

added_files本质上是一个包含元组的列表,在我的例子中,我只想添加一个图像,但您可以使用添加多个 ico、png 或 jpg('app/img/*.ico', 'app/img')您也可以像这样创建另一个元组以added_files = [ (), (), ()]具有多个导入

元组的第一部分定义您想要添加什么文件或什么类型的文件以及在哪里可以找到它们。将此视为 CTRL+C

元组的第二部分告诉 Pyinstaller 创建路径“app/img/”并将文件放置在该目录中,该目录与运行 .exe 时创建的任何临时目录相关。将此视为 CTRL+V

在 下a = Analysis([main...,我已经设置了datas=added_files,最初它曾经是datas=[],但我们希望导入导入列表,所以我们传入自定义导入。

除非您想要 EXE 的特定图标,否则您不需要这样做,在规范文件的底部,我告诉 Pyinstaller 使用选项设置 exe 的应用程序图标icon='app\\img\\app_icon.ico'

added_files = [
    ('app/img/app_icon.ico','app/img/')
]
a = Analysis(['main.py'],
             pathex=['D:\\Github Repos\\Processes-Killer\\Process Killer'],
             binaries=[],
             datas=added_files,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='Process Killer',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True , uac_admin=True, icon='app\\img\\app_icon.ico')
Run Code Online (Sandbox Code Playgroud)

编译为EXE

我很懒;我不喜欢输入不必要的东西。我创建了一个 .bat 文件,只需单击即可。您不必这样做,即使没有它,此代码也可以在命令提示符 shell 中正常运行。

由于 .spec 文件包含我们所有的编译设置和参数(也称为选项),我们只需将该 .spec 文件提供给 Pyinstaller 即可。

pyinstaller.exe "Process Killer.spec"
Run Code Online (Sandbox Code Playgroud)


dil*_*ten 10

也许我错过了一步或做错了什么,但上面的方法,没有将数据文件与PyInstaller捆绑到一个exe文件中.让我分享我所做的步骤.

  1. 步骤:使用导入sys和os模块将上述方法之一写入py文件.我试过了他们两个.最后一个是:

    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)
    
    Run Code Online (Sandbox Code Playgroud)
  2. 步骤:将pyi-makespec file.py写入控制台,以创建file.spec文件.

  3. 步骤:使用Notepad ++打开file.spec,添加如下数据文件:

    a = Analysis(['C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.py'],
                 pathex=['C:\\Users\\TCK\\Desktop\\Projeler'],
                 binaries=[],
                 datas=[],
                 hiddenimports=[],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher)
    #Add the file like the below example
    a.datas += [('Converter-GUI.ico', 'C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico', 'DATA')]
    pyz = PYZ(a.pure, a.zipped_data,
         cipher=block_cipher)
    exe = EXE(pyz,
              a.scripts,
              exclude_binaries=True,
              name='Converter-GUI',
              debug=False,
              strip=False,
              upx=True,
              #Turn the console option False if you don't want to see the console while executing the program.
              console=False,
              #Add an icon to the program.
              icon='C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico')
    
    coll = COLLECT(exe,
                   a.binaries,
                   a.zipfiles,
                   a.datas,
                   strip=False,
                   upx=True,
                   name='Converter-GUI')
    
    Run Code Online (Sandbox Code Playgroud)
  4. 步骤:我按照上面的步骤,然后保存spec文件.最后打开控制台并编写了pyinstaller file.spec(在我的例子中,file = Converter-GUI).

结论:dist文件夹中仍有多个文件.

注意:我使用的是Python 3.5.

编辑:最后它与Jonathan Reinhart的方法一起使用.

  1. 步骤:使用导入sys和os将以下代码添加到python文件中.

    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)
    
    Run Code Online (Sandbox Code Playgroud)
  2. 步骤:通过添加文件路径调用上述函数:

    image_path = resource_path("Converter-GUI.ico")
    
    Run Code Online (Sandbox Code Playgroud)
  3. 步骤:将调用函数的上述变量写入代码所需路径的位置.在我的情况下它是:

        self.window.iconbitmap(image_path)
    
    Run Code Online (Sandbox Code Playgroud)
  4. 步骤:在python文件的同一目录中打开控制台,编写如下代码:

        pyinstaller --onefile your_file.py
    
    Run Code Online (Sandbox Code Playgroud)
  5. 步骤:打开python文件的.spec文件并附加a.datas数组并将该图标添加到exe类,该类在上面的第3步编辑之前给出.
  6. 步骤:保存并退出路径文件.转到包含spec和py文件的文件夹.再次打开控制台窗口并键入以下命令:

        pyinstaller your_file.spec
    
    Run Code Online (Sandbox Code Playgroud)

在6.步骤之后,您的一个文件就可以使用了.

  • 哦,我很开心,它解决了你的问题.:) (2认同)

Bob*_*igh 6

而不是按照建议重写我的所有路径代码,我更改了工作目录:

if getattr(sys, 'frozen', False):
    os.chdir(sys._MEIPASS)
Run Code Online (Sandbox Code Playgroud)

只需在代码的开头添加这两行,就可以保留原样.

  • 不.好的应用程序应该很少更改工作目录.有更好的方法. (2认同)

muy*_*tan 6

我一直在处理这个问题长一段时间(好吧,长时间)。我几乎搜索了所有来源,但事情并没有在我脑海中形成模式。

最后,我想我已经找到了要遵循的确切步骤,我想分享一下。

请注意,我的回答使用了其他人对此问题的回答的信息。

如何创建 python 项目的独立可执行文件。

假设我们有一个project_folder,文件树如下:

project_folder/
    main.py
    xxx.py # another module
    yyy.py # another module
    sound/ # directory containing the sound files
    img/ # directory containing the image files
    venv/ # if using a venv
Run Code Online (Sandbox Code Playgroud)

首先,假设您已将路径sound/img/文件夹定义为变量sound_dirimg_dir如下所示:

img_dir = os.path.join(os.path.dirname(__file__), "img")
sound_dir = os.path.join(os.path.dirname(__file__), "sound")
Run Code Online (Sandbox Code Playgroud)

您必须更改它们,如下所示:

img_dir = resource_path("img")
sound_dir = resource_path("sound")
Run Code Online (Sandbox Code Playgroud)

其中,resource_path()在脚本顶部定义为:

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)
Run Code Online (Sandbox Code Playgroud)

如果使用 venv,则激活虚拟 env,

如果您还没有安装 pyinstaller,请通过:pip3 install pyinstaller

运行:pyi-makespec --onefile main.py为编译和构建过程创建规范文件。

这会将文件层次结构更改为:

project_folder/
    main.py
    xxx.py # modules
    xxx.py # modules
    sound/ # directory containing the sound files
    img/ # directory containing the image files
    venv/ # if using a venv
    main.spec
Run Code Online (Sandbox Code Playgroud)

打开(带编辑器)main.spec

在它的顶部,插入:

added_files = [

("sound", "sound"),
("img", "img")

]
Run Code Online (Sandbox Code Playgroud)

然后,将行更改datas=[],datas=added_files,

有关在此处完成的操作的详细信息,main.spec请参见此处。

pyinstaller --onefile main.spec

而这一切,你可以运行mainproject_folder/dist任何地方,而无需其文件夹中的任何东西。您只能分发该main文件。现在,一个真正的独立.