问题:使用PyInstaller捆绑数据文件(–onefile)

我正在尝试使用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')]     

我应该补充一点,我也尝试过不要将它们放在子文件夹中,也没有什么不同。

编辑: 由于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.


回答 0

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

回答 1

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"

回答 2

在未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)

回答 3

也许我错过了一个步骤或做错了什么,但是上面的方法并没有将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)
  2. 步骤:将pyi-makespec file.py写入控制台,以创建file.spec文件。

  3. 步骤:使用记事本++打开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')
  4. 步骤:我按照上述步骤操作,然后保存了规格文件。最后打开控制台并编写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)
  2. 步骤:通过添加文件路径调用上述函数:

    image_path = resource_path("Converter-GUI.ico")
  3. 步骤:将上面的变量编写为将函数调用到代码需要路径的位置。就我而言:

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

        pyinstaller --onefile your_file.py
  5. 步骤:打开python文件的.spec文件,并附加a.datas数组,并将图标添加到exe类,该类在第3步编辑之前已在上面给出。
  6. 步骤:保存并退出路径文件。转到包含spec和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.


回答 4

为了更改建议的所有路径代码,我更改了工作目录:

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.


回答 5

稍微修改接受的答案。

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)

回答 6

我在PyInstaller上看到的最常见的投诉/问题是“我的代码找不到我确实包含在捆绑软件中的数据文件,它在哪里?”,而且很难看到您的代码在哪里。正在搜索,因为提取的代码位于临时位置,退出时将其删除。使用@Jonathon Reinhart的代码,添加以下代码以查看 onefile中包含的内容及其所在位置。resource_path()

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)

回答 7

我发现现有的答案令人困惑,并且花了很长时间解决问题所在。这是我发现的所有内容的汇总。

运行我的应用程序时,出现错误Failed to execute script foo(如果foo.py是主文件)。要解决此问题,请不要使用来运行PyInstaller --noconsole(或进行编辑main.spec以更改console=False=> console=True)。这样,从命令行运行可执行文件,您将看到失败。

首先要检查的是它是否正确包装了额外的文件。您应该添加元组,例如('x', 'x')是否要包含文件夹x

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

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

要解决此问题,此问题的任何答案都可以解决。就个人而言,我发现它们混合在一起起作用:在主入口点文件中有条件地更改目录的第一件事,其他所有内容都保持原样:

if 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)


回答 8

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

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.


回答 9

我已经(长时间)处理这个问题了。我搜索了几乎所有的资源,但事情并没有在我的脑海中浮现。

最后,我想我想出了我想分享的确切步骤。

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

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

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

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

首先,假设您已将变量的路径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创建用于编译和构建过程的spec文件。

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

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

打开(带有edior)main.spec

在其顶部插入:

added_files = [

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

]

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

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

pyinstaller --onefile main.spec

就是这样,您可以mainproject_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 informations 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 # modules
    xxx.py # modules
    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

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 here.

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.


回答 10

使用Max本文中有关添加额外的数据文件(如图像或声音)以及我自己的研究/测试的出色答案,我弄清楚了我认为添加此类文件的最简单方法。

如果你想看到一个活生生的例子,我的仓库是这里 GitHub上。

注意:这是用于在pyinstaller中使用--onefileor -F命令进行编译。

我的环境如下。


分两步解决问题

为了解决该问题,我们需要专门告诉Pyinstaller,我们还有一些其他文件需要与应用程序“捆绑”在一起。

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

话虽如此,我们需要一个允许我们拥有相对路径的功能。使用“ 最大发布 ”功能,我们可以轻松解决相对路径。

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)

我们将使用上述功能,因此当应用程序以脚本或冻结EXE的形式运行时,应用程序图标就会显示出来。

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

下一步是我们需要指导Pyinstaller在编译时在哪里可以找到多余的文件,以便在运行应用程序时在temp目录中创建它们。

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

首先,您必须已经有一个.spec文件。就我而言,我可以通过运行pyinstaller额外的args 创建所需的内容,您可以在此处找到额外的args 。因此,我的规格文件可能看起来与您的有些不同,但是在解释了重要内容之后,我将其全部发布以供参考。

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

元组的第一部分定义您要添加的文件或文件的类型以及在何处找到它们。认为这是CTRL + C

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

,我设置了datas=added_files,最初它曾经是,datas=[]但是我们想要导入的列表是导入的,所以我们传入自定义导入。

除非您需要EXE的特定图标,否则您无需执行此操作,在spec文件的底部,我告诉Pyinstaller使用option为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')

编译为EXE

我很懒。我不喜欢输入太多东西。我创建了一个.bat文件,只需单击即可。您不必这样做,此代码将在没有它的情况下在命令提示符外壳中运行。

由于.spec文件包含我们所有的编译设置和args(aka选项),我们只需要将该.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

, 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"

回答 11

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

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)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。