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

发布于 2024-12-08 15:05:54 字数 679 浏览 1 评论 0 原文

我正在尝试使用 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 更新,将新答案标记为正确。

I'm trying to build a one-file EXE with PyInstaller which is to include an image and an icon. I cannot for the life of me get it to work with --onefile.

If I do --onedir it works all works very well.
When I use --onefile, it can't find the referenced additional files (when running the compiled EXE). It finds the DLLs and everything else fine, just not the two images.

I've looked in the temp-dir generated when running the EXE (\Temp\_MEI95642\ for example) and the files are indeed in there. When I drop the EXE in that temp-directory it finds them. Very perplexing.

This is what I've added to the .spec file

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

I should add that I have tried not putting them in subfolders as well, didn't make a difference.

Edit: Marked newer answer as correct due to PyInstaller update.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

梦明 2024-12-15 15:05:54

较新版本的 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)

Newer versions of PyInstaller do not set the env variable anymore, so Shish's excellent answer will not work. Now the path gets set as 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)
拥抱没勇气 2024-12-15 15:05:54

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"

pyinstaller unpacks your data into a temporary folder, and stores this directory path in the _MEIPASS2 environment variable. To get the _MEIPASS2 dir in packed-mode and use the local directory in unpacked (development) mode, I use this:

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

Output:

# 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"
梦里的微风 2024-12-15 15:05:54

在应用程序未安装 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)

All of the other answers use the current working directory in the case where the application is not PyInstalled (i.e. sys._MEIPASS is not set). That is wrong, as it prevents you from running your application from a directory other than the one where your script is.

A better solution:

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)
深爱成瘾 2024-12-15 15:05:54

我已经处理这个问题很长一段时间了(嗯,非常)。我搜索了几乎所有的资料来源,但事情并没有在我的脑海中形成一种模式。

最后,我想我已经弄清楚了要遵循的确切步骤,我想分享。

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

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

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

<前><代码>项目文件夹/
主要.py
xxx.py # 另一个模块
yyy.py # 另一个模块
sound/ # 包含声音文件的目录
img/ # 包含图像文件的目录
venv/ # 如果使用 venv

首先,假设您已将 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")

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

img_dir = resource_path("img")
sound_dir = resource_path("sound")

其中,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)

如果使用 venv,请激活虚拟环境,

如果没有,请安装 pyinstaller然而,通过:pip3 install pyinstaller

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

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

<前><代码>项目文件夹/
主要.py
xxx.py # 模块
xxx.py # 模块
sound/ # 包含声音文件的目录
img/ # 包含图像文件的目录
venv/ # 如果使用 venv
main.spec #<<<注意这个新文件!

打开(使用编辑器)main.spec

在其顶部插入:

added_files = [

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

]

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

有关 main.spec 上完成的操作的详细信息,请参阅 使用 PyInstaller 文档中的规范文件部分

运行 pyinstaller --onefile main.spec

就这样,您可以在 中运行 main任何地方的project_folder/dist,其文件夹中没有任何其他内容。您只能分发该 main 文件。现在,它是一个真正的独立

I have been dealing with this issue for a long(well, very long) time. I've searched almost every source but things were not getting in a pattern in my head.

Finally, I think I have figured out exact steps to follow, I wanted to share.

Note that, my answer uses information on the answers of others on this question.

How to create a standalone executable of a python project.

Assume, we have a project_folder and the file tree is as follows:

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

First of all, let's say you have defined your paths to sound/ and img/ folders into variables sound_dir and img_dir as follows:

img_dir = os.path.join(os.path.dirname(__file__), "img")
sound_dir = os.path.join(os.path.dirname(__file__), "sound")

You have to change them, as follows:

img_dir = resource_path("img")
sound_dir = resource_path("sound")

Where, resource_path() is defined in the top of your script as:

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)

Activate virtual env if using a venv,

Install pyinstaller if you didn't yet, by: pip3 install pyinstaller.

Run: pyi-makespec --onefile main.py to create the spec file for the compile and build process.

This will change file hierarchy to:

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 #<<< NOTICE this new file!

Open(with an edior) main.spec:

At top of it, insert:

added_files = [

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

]

Then, change the line of datas=[], to datas=added_files,

For the details of the operations done on main.spec see the Using Spec Files Section in the PyInstaller Documentation

Run pyinstaller --onefile main.spec

And that is all, you can run main in project_folder/dist from anywhere, without having anything else in its folder. You can distribute only that main file. It is now, a true standalone.

你的笑 2024-12-15 15:05:54

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

  1. 步骤:将上述方法之一写入您的 py 文件中,并导入 sys 和 os 模块。我都尝试过。最后一个是:

    def 资源路径(相对路径):
    """ 获取资源的绝对路径,适用于 dev 和 PyInstaller """
        base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        返回 os.path.join(base_path,relative_path)
    
  2. step: Write, pyi-makespec file.py, to the console, to create a file.spec file.

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

    a = Analysis(['C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.py'],
                 pathex=['C:\\Users\\TCK\\Desktop\\Projeler'],
                 二进制文件=[],
                 数据=[],
                 隐藏导入=[],
                 钩子路径=[],
                 运行时钩子=[],
                 排除=[],
                 win_no_prefer_redirects=假,
                 win_private_assemblies=假,
                 密码=块密码)
    #添加文件,如下例所示
    a.datas += [('Converter-GUI.ico', 'C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico', 'DATA')]
    pyz = PYZ(a.pure, a.zipped_data,
         密码=块密码)
    exe = EXE(pyz,
              a. 脚本,
              except_binaries=真,
              name='Converter-GUI',
              调试=假,
              条=假,
              upx=真,
              #如果您不想在执行程序时看到控制台,请将控制台选项设置为False。
              控制台=假,
              #给程序添加一个图标。
              图标='C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico')
    
    科尔 = 收集(exe,
                   a. 二进制文件,
                   a.zip 文件,
                   a. 数据,
                   条=假,
                   upx=真,
                   name='Converter-GUI')
    
  4. 步骤:我按照上述步骤操作,然后保存了spec 文件。最后打开控制台并写入,pyinstaller file.spec(在我的例子中,file=Converter-GUI)。

结论:dist文件夹中仍然存在多个文件。

注意:我使用的是Python 3.5。

编辑:最后它适用于 Jonathan Reinhart 的方法。

  1. 步骤:将以下代码添加到您的 python 文件中,并导入 sys 和 os。

    def 资源路径(相对路径):
    """ 获取资源的绝对路径,适用于 dev 和 PyInstaller """
        base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        返回 os.path.join(base_path,relative_path)
    
  2. 步骤:调用上述函数并添加文件路径:

    image_path = resources_path("Converter-GUI.ico")
    
  3. 步骤:将调用函数的上述变量写入代码需要路径的位置。就我而言,它是:

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

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

     pyinstaller your_file.spec
    

第 6 步之后,您的文件就可以使用了。

Perhaps i missed a step or did something wrong but the methods which are above, didn't bundle data files with PyInstaller into one exe file. Let me share the steps what i have done.

  1. step:Write one of the above methods into your py file with importing sys and os modules. I tried both of them. The last one is:

    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)
    
  2. step: Write, pyi-makespec file.py, to the console, to create a file.spec file.

  3. step: Open, file.spec with Notepad++ to add the data files like below:

    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')
    
  4. step: I followed the above steps, then saved the spec file. At last opened the console and write, pyinstaller file.spec (in my case, file=Converter-GUI).

Conclusion: There's still more than one file in the dist folder.

Note: I'm using Python 3.5.

EDIT: Finally it works with Jonathan Reinhart's method.

  1. step: Add the below codes to your python file with importing sys and 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)
    
  2. step: Call the above function with adding the path of your file:

    image_path = resource_path("Converter-GUI.ico")
    
  3. step: Write the above variable that call the function to where your codes need the path. In my case it's:

        self.window.iconbitmap(image_path)
    
  4. step: Open the console in the same directory of your python file, write the codes like below:

        pyinstaller --onefile your_file.py
    
  5. step: Open the .spec file of the python file and append the a.datas array and add the icon to the exe class, which was given above before the edit in 3'rd step.
  6. step: Save and exit the path file. Go to your folder which include the spec and py file. Open again the console window and type the below command:

        pyinstaller your_file.spec
    

After the 6. step your one file is ready to use.

醉南桥 2024-12-15 15:05:54

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

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

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

我的环境是接下来。


分两步解决问题

要解决这个问题,我们需要专门告诉 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)

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

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

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

我们可以通过两种方式解决此问题,如 文档< /a>,但我个人更喜欢管理我自己的 .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< /code>,最初它曾经是 datas=[] 但我们希望导入导入列表,因此我们传入自定义导入,

您不需要这样做 。除非您想要 EXE 的特定图标,位于规范的底部文件我告诉 Pyinstaller 使用选项 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')

编译为 EXE

我很懒;我不喜欢输入更多内容,我已经创建了一个只需单击即可的文件,您不必这样做,

因为没有 . spec 文件包含我们所有的编译设置& args (又名选项)我们只需将该 .spec 文件提供给 Pyinstaller 即可。

pyinstaller.exe "Process Killer.spec"

Using the excellent answer from Max and This post about adding extra data files like images or sound & my own research/testing, I've figured out what I believe is the easiest way to add such files.

If you would like to see a live example, my repository is here on GitHub.

Note: this is for compiling using the --onefile or -F command with pyinstaller.

My environment is as follows.


Solving the problem in 2 steps

To solve the issue we need to specifically tell Pyinstaller that we have extra files that need to be "bundled" with the application.

We also need to be using a 'relative' path, so the application can run properly when it's running as a Python Script or a Frozen EXE.

With that being said we need a function that allows us to have relative paths. Using the function that Max Posted we can easily solve the relative pathing.

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)

We would use the above function like this so the application icon shows up when the app is running as either a Script OR Frozen EXE.

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

The next step is that we need to instruct Pyinstaller on where to find the extra files when it's compiling so that when the application is run, they get created in the temp directory.

We can solve this issue two ways as shown in the documentation, but I personally prefer managing my own .spec file so that's how we're going to do it.

First, you must already have a .spec file. In my case, I was able to create what I needed by running pyinstaller with extra args, you can find extra args here. Because of this, my spec file may look a little different than yours but I'm posting all of it for reference after I explain the important bits.

added_files is essentially a List containing Tuple's, in my case I'm only wanting to add a SINGLE image, but you can add multiple ico's, png's or jpg's using ('app/img/*.ico', 'app/img') You may also create another tuple like soadded_files = [ (), (), ()] to have multiple imports

The first part of the tuple defines what file or what type of file's you would like to add as well as where to find them. Think of this as CTRL+C

The second part of the tuple tells Pyinstaller, to make the path 'app/img/' and place the files in that directory RELATIVE to whatever temp directory gets created when you run the .exe. Think of this as CTRL+V

Under a = Analysis([main..., I've set datas=added_files, originally it used to be datas=[] but we want out the list of imports to be, well, imported so we pass in our custom imports.

You don't need to do this unless you want a specific icon for the EXE, at the bottom of the spec file I'm telling Pyinstaller to set my application icon for the exe with the option 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')

Compiling to EXE

I'm very lazy; I don't like typing things more than I have to. I've created a .bat file that I can just click. You don't have to do this, this code will run in a command prompt shell just fine without it.

Since the .spec file contains all of our compiling settings & args (aka options) we just have to give that .spec file to Pyinstaller.

pyinstaller.exe "Process Killer.spec"
猥︴琐丶欲为 2024-12-15 15:05:54

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

if getattr(sys, 'frozen', False):
    os.chdir(sys._MEIPASS)

只需在代码开头添加这两行即可,其余部分保持原样。

Instead for rewriting all my path code as suggested, I changed the working directory:

if getattr(sys, 'frozen', False):
    os.chdir(sys._MEIPASS)

Just add those two lines at the beginning of your code, you can leave the rest as is.

辞旧 2024-12-15 15:05:54

对已接受的答案稍作修改。

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)

    return os.path.join(os.path.abspath("."), relative_path)

Slight modification to the accepted answer.

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)

    return os.path.join(os.path.abspath("."), relative_path)
终难愈 2024-12-15 15:05:54

另一个解决方案是创建一个运行时挂钩,它将复制(或移动)您的数据(文件/文件夹)到存储可执行文件的目录。该钩子是一个简单的 python 文件,它几乎可以在执行应用程序之前执行任何操作。为了设置它,您应该使用 pyinstaller 的 --runtime-hook=my_hook.py 选项。因此,如果您的数据是 images 文件夹,则应运行以下命令:

pyinstaller.py --onefile -F --add-data=images;images --runtime-hook=cp_images_hook.py main.py

cp_images_hook.py 可能如下所示:

import sys
import os
import shutil

path = getattr(sys, '_MEIPASS', os.getcwd())

full_path = path+"\\images"
try:
    shutil.move(full_path, ".\\images")
except:
    print("Cannot create 'images' folder. Already exists.")

在每次执行之前,images 文件夹都会移动到当前目录(来自 _MEIPASS 文件夹),因此可执行文件始终可以访问它。
这样就不需要修改项目的代码。

第二种解决方案

您可以利用运行时挂钩机制并更改当前目录,根据一些开发人员的说法,这不是一个好的做法,但它工作得很好。

钩子代码可以在下面找到:

import sys
import os

path = getattr(sys, '_MEIPASS', os.getcwd())   
os.chdir(path)

Another solution is to make a runtime hook, which will copy(or move) your data (files/folders) to the directory at which the executable is stored. The hook is a simple python file that can almost do anything, just before the execution of your app. In order to set it, you should use the --runtime-hook=my_hook.py option of pyinstaller. So, in case your data is an images folder, you should run the command:

pyinstaller.py --onefile -F --add-data=images;images --runtime-hook=cp_images_hook.py main.py

The cp_images_hook.py could be something like this:

import sys
import os
import shutil

path = getattr(sys, '_MEIPASS', os.getcwd())

full_path = path+"\\images"
try:
    shutil.move(full_path, ".\\images")
except:
    print("Cannot create 'images' folder. Already exists.")

Before every execution the images folder is moved to the current directory (from the _MEIPASS folder), so the executable will always have access to it.
In that way there is no need to modify your project's code.

Second Solution

You can take advantage of the runtime-hook mechanism and change the current directory, which is not a good practice according to some developers, but it works fine.

The hook code can be found below:

import sys
import os

path = getattr(sys, '_MEIPASS', os.getcwd())   
os.chdir(path)
皇甫轩 2024-12-15 15:05:54

我在 PyInstaller 上看到的最常见的抱怨/问题是“我的代码找不到我明确包含在捆绑包中的数据文件,它在哪里?”,并且很难看出您的代码是什么/在哪里正在搜索,因为提取的代码位于临时位置,并在退出时被删除。使用 @Jonathon Reinhart 的 resource_path() 添加这段代码来查看您的 onefile 中包含的内容及其位置

for root, dirs, files in os.walk(resource_path("")):
    print(root)
    for file in files:
        print( "  ",file)

The most common complaint/question I've seen wrt PyInstaller is "my code can't find a data file which I definitely included in the bundle, where is it?", and it isn't easy to see what/where your code is searching because the extracted code is in a temp location and is removed when it exits. Add this bit of code to see what's included in your onefile and where it is, using @Jonathon Reinhart's resource_path()

for root, dirs, files in os.walk(resource_path("")):
    print(root)
    for file in files:
        print( "  ",file)
少跟Wǒ拽 2024-12-15 15:05:54

如果您仍然尝试将与可执行文件相关的文件而不是放在临时目录中,则需要自己复制它。这就是我最终完成它的方式。

https://stackoverflow.com/a/59415662/999943

您在规范文件中添加一个步骤来执行文件系统复制到 DISTPATH 变量。

希望有帮助。

If you are still trying to put files relative to your executable instead of in the temp directory, you need to copy it yourself. This is how I ended up getting it done.

https://stackoverflow.com/a/59415662/999943

You add a step in the spec file that does a filesystem copy to the DISTPATH variable.

Hope that helps.

吐个泡泡 2024-12-15 15:05:54

我使用这个基于最大解决方案

def getPath(filename):
    import os
    import sys
    from os import chdir
    from os.path import join
    from os.path import dirname
    from os import environ
    
    if hasattr(sys, '_MEIPASS'):
        # PyInstaller >= 1.6
        chdir(sys._MEIPASS)
        filename = join(sys._MEIPASS, filename)
    elif '_MEIPASS2' in environ:
        # PyInstaller < 1.6 (tested on 1.5 only)
        chdir(environ['_MEIPASS2'])
        filename = join(environ['_MEIPASS2'], filename)
    else:
        chdir(dirname(sys.argv[0]))
        filename = join(dirname(sys.argv[0]), filename)
        
    return filename

i use this based on max solution

def getPath(filename):
    import os
    import sys
    from os import chdir
    from os.path import join
    from os.path import dirname
    from os import environ
    
    if hasattr(sys, '_MEIPASS'):
        # PyInstaller >= 1.6
        chdir(sys._MEIPASS)
        filename = join(sys._MEIPASS, filename)
    elif '_MEIPASS2' in environ:
        # PyInstaller < 1.6 (tested on 1.5 only)
        chdir(environ['_MEIPASS2'])
        filename = join(environ['_MEIPASS2'], filename)
    else:
        chdir(dirname(sys.argv[0]))
        filename = join(dirname(sys.argv[0]), filename)
        
    return filename
英雄似剑 2024-12-15 15:05:54

对于那些仍在寻找更新答案的人,请看这里:

文档,有一个 有关访问添加的数据文件的部分
这是它的简短而甜蜜的。


您需要导入 pkgutil 并找到将数据文件添加到的文件夹;即元组中添加到规范文件中的第二个字符串:

datas = [("path/to/mypackage/data_file.txt", "path/to/mypackage")]

了解添加数据文件的位置,然后可以将其作为二进制数据读取,并根据需要对其进行解码。举个例子:

文件结构:

mypackage
      __init__.py  # This is a MUST in order for the package to be registered
      data_file.txt  # The data file you've added

data_file.txt

Hello world!

main.py

import pkgutil

file = pkgutil.get_data("mypackage", "data_file.txt")
contents = file.decode("utf-8")
print(contents)  # Hello world!

参考资料:

For those of whom are still looking for a more recent answer, here you go:

In the documentation, there's a section on accessing added data files.
Here is the short and sweet of it.


You'll want to import pkgutil and locate which folder you added the datafile to; i.e. the second string in the tuple which was added to the spec file:

datas = [("path/to/mypackage/data_file.txt", "path/to/mypackage")]

Knowing where you added the data file can then be used for reading it in as binary data, and decoding it as you wish. Take this example:

File structure:

mypackage
      __init__.py  # This is a MUST in order for the package to be registered
      data_file.txt  # The data file you've added

data_file.txt

Hello world!

main.py

import pkgutil

file = pkgutil.get_data("mypackage", "data_file.txt")
contents = file.decode("utf-8")
print(contents)  # Hello world!

References:

挽清梦 2024-12-15 15:05:54

最后 !基于 Luca 的解决方案,

我最终能够在 --onefile pyinstaller 中包含的目录中执行文件。

只需使用他发布的方法,您甚至可以在文件中输入整个目录名称,它仍然有效。

您需要的第二件事实际上是在 pyinstaller 中添加您想要的目录/文件。只需使用如下参数:

--add-data "yourDir/And/OrFile;yourDir"

FINALLY ! based on solution from Luca

I was able to finaly execute files inside included directory in --onefile pyinstaller.

Just use the method he posted and you can even enter a whole directory name to files and it still works.

Second thing you will need is actually add the directory/files you want inside pyinstaller. Just use args like:

--add-data "yourDir/And/OrFile;yourDir"
南街女流氓 2024-12-15 15:05:54

快刀斩乱麻:os.getcwd()__file__

@JonathonReinhart 强烈反对使用 os.getcwd()< /code> 在脚本中,尤其是反对更改它。这是有充分理由的。但是,正如@Guimoute 所指出的,如果您想将resource_path() 函数放入main.py 之外的某个模块中,你可能会遇到问题。这是解决这个问题的一种方法。

解决方案

我的解决方案遵循@DisappointedByUnaccountableMod的建议,但没有在main.c中定义新函数。这是实现的关键:

  • ./main.py

我将以下变量定义添加到主脚本文件中。

import os

# Import the resource_path helper function from wherever it is
from src.helpers import resource_path

# Store the location of the directory where the script is running
# i.e. NOT where it is being run from
__main_script_dir__ = os.path.dirname(__file__)
  • ./src/helpers.py
import os
import sys

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    
    # Load dynamically to avoid circular import
    from main import __main_script_dir__

    base_path = getattr(sys, '_MEIPASS', os.path.abspath(__main_script_dir__))
    # print(f"BASE_PATH: {base_path}")

    return os.path.abspath(os.path.join(base_path, relative_path))

然后,无论您在何处使用资源,只要您想将其捆绑到 .exe 文件中,您都可以调用 resource_path (relative_path_to_your_resource),它将始终返回程序所需的路径,具体取决于程序是作为可执行文件运行还是仅作为脚本运行。

Cutting the Gordian Knot re: os.getcwd() vs. __file__

@JonathonReinhart argues strongly against using os.getcwd() in scripts, and especially against changing it. This is for all the right reasons. However, as pointed out by @Guimoute if you want to place the resource_path() function into a module somewhere outside of your main.py, you might run into problems. Here's one way of solving this.

Solution

My solution follows @DisappointedByUnaccountableMod's recommendation, but without defining a new function in main. Here's the crux of the implementation:

  • ./main.py:

I add the following variable definition into the main script file.

import os

# Import the resource_path helper function from wherever it is
from src.helpers import resource_path

# Store the location of the directory where the script is running
# i.e. NOT where it is being run from
__main_script_dir__ = os.path.dirname(__file__)
  • ./src/helpers.py:
import os
import sys

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    
    # Load dynamically to avoid circular import
    from main import __main_script_dir__

    base_path = getattr(sys, '_MEIPASS', os.path.abspath(__main_script_dir__))
    # print(f"BASE_PATH: {base_path}")

    return os.path.abspath(os.path.join(base_path, relative_path))

Then, wherever you use a resource, which you would like to bundle into your .exe file, you can just call resource_path(relative_path_to_your_resource), which will always return the path your program needs depending on whether it's run as an executable or just as a script.

呆° 2024-12-15 15:05:54

我发现现有的答案令人困惑,并花了很长时间才找出问题所在。这是我找到的所有内容的汇编。

当我运行应用程序时,出现错误无法执行脚本 foo(如果 foo.py 是主文件)。要解决此问题,请不要使用 --noconsole 运行 PyInstaller(或编辑 main.spec 更改 console=False => console=True)。这样,从命令行运行可执行文件,您就会看到失败。

首先要检查的是它是否正确打包了您的额外文件。如果您希望包含文件夹 x,您应该添加像 ('x', 'x') 这样的元组。

崩溃后,不要单击“确定”。如果您使用的是 Windows,则可以使用 搜索一切。查找您的文件之一(例如sword.png)。您应该找到解压文件的临时路径(例如C:\Users\ashes999\AppData\Local\Temp\_MEI157682\images\sword.png)。您可以浏览此目录并确保它包含所有内容。如果您无法通过这种方式找到它,请查找诸如 main.exe.manifest (Windows) 或 python35.dll(如果您使用的是 Python 3.5)之类的内容。

如果安装程序包含所有内容,则下一个可能的问题是文件 I/O:您的 Python 代码正在可执行文件的目录(而不是临时目录)中查找文件。

为了解决这个问题,这个问题的任何答案都有效。就我个人而言,我发现它们都可以工作:在主入口点文件中首先有条件地更改目录,其他一切都按原样工作:


如果 hasattr(sys, '_MEIPASS'):
os.chdir(sys._MEIPASS)

I found the existing answers confusing, and took a long time to work out where the problem is. Here's a compilation of everything I found.

When I run my app, I get an error Failed to execute script foo (if foo.py is the main file). To troubleshoot this, don't run PyInstaller with --noconsole (or edit main.spec to change console=False => console=True). With this, run the executable from a command-line, and you'll see the failure.

The first thing to check is that it's packaging up your extra files correctly. You should add tuples like ('x', 'x') if you want the folder x to be included.

After it crashes, don't click OK. If you're on Windows, you can use Search Everything. Look for one of your files (eg. sword.png). You should find the temporary path where it unpacked the files (eg. C:\Users\ashes999\AppData\Local\Temp\_MEI157682\images\sword.png). You can browse this directory and make sure it included everything. If you can't find it this way, look for something like main.exe.manifest (Windows) or python35.dll (if you're using Python 3.5).

If the installer includes everything, the next likely problem is file I/O: your Python code is looking in the executable's directory, instead of the temp directory, for files.

To fix that, any of the answers on this question work. Personally, I found a mixture of them all to work: change directory conditionally first thing in your main entry-point file, and everything else works as-is:


if hasattr(sys, '_MEIPASS'):
os.chdir(sys._MEIPASS)

咆哮 2024-12-15 15:05:54

以上都不适合我。它不允许我使用规范文件和一个文件开关运行 pyinstaller。我是在windows下编译的。我的应用程序组织如下:

main.py

带有 .png 文件的子文件夹图像

带有 csv 文件的子文件夹数据

这是对我有用的命令:
pyinstaller -F --add-data images\*.png;images --add-data data\*.csv;data main.py

可执行文件位于 dist 文件夹中。

我希望它能帮助别人!

None of the above worked for me. It wouldn't let me run pyinstaller with a spec file and the one file switch. I compiled on windows. I have my app organized like this:

main.py

subfolder images with .png files

subfolder data with csv files

This is the command that worked for me:
pyinstaller -F -–add-data images\*.png;images --add-data data\*.csv;data main.py

The executable is in the dist folder.

I hope it helps someone!

顾北清歌寒 2024-12-15 15:05:54

这将为您提供当前目录路径。

import sys, os

def get_current_dir_path():
    if getattr(sys, 'frozen', False):
        # If the application is run as a bundle (e.g., PyInstaller, cx_Freeze)
        current_dir_path = os.path.dirname(sys.executable)
    else:
        # If the application is run as a script
        current_dir_path = os.path.dirname(os.path.abspath(__file__))
    return current_dir_path

current_dir_path = get_current_dir_path()

# Now you can use 'current_dir_path'
# Whether you run your script or executable, this will provide you with the current directory path.
print(current_dir_path)

This will provide you with the current directory path.

import sys, os

def get_current_dir_path():
    if getattr(sys, 'frozen', False):
        # If the application is run as a bundle (e.g., PyInstaller, cx_Freeze)
        current_dir_path = os.path.dirname(sys.executable)
    else:
        # If the application is run as a script
        current_dir_path = os.path.dirname(os.path.abspath(__file__))
    return current_dir_path

current_dir_path = get_current_dir_path()

# Now you can use 'current_dir_path'
# Whether you run your script or executable, this will provide you with the current directory path.
print(current_dir_path)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文