我正在尝试使用 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.
发布评论
评论(18)
较新版本的 PyInstaller 不再设置
env
变量,因此 Shish 的出色答案将不起作用。现在路径被设置为 sys._MEIPASS :Newer versions of PyInstaller do not set the
env
variable anymore, so Shish's excellent answer will not work. Now the path gets set assys._MEIPASS
:pyinstaller 将您的数据解压到临时文件夹中,并将该目录路径存储在
_MEIPASS2
环境变量中。要在打包模式下获取_MEIPASS2
目录并在解包(开发)模式下使用本地目录,我使用以下命令:输出:
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:Output:
在应用程序未安装 PyInstalled 的情况下(即未设置 sys._MEIPASS ),所有其他答案都使用当前工作目录。这是错误的,因为它会阻止您从脚本所在目录以外的目录运行应用程序。
更好的解决方案:
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:
我已经处理这个问题很长一段时间了(嗯,非常)。我搜索了几乎所有的资料来源,但事情并没有在我的脑海中形成一种模式。
最后,我想我已经弄清楚了要遵循的确切步骤,我想分享。
请注意,我的答案使用了其他人对此问题的答案的信息。
如何创建 python 项目的独立可执行文件。
假设我们有一个project_folder,文件树如下:
首先,假设您已将
sound/
和img/
文件夹的路径定义为变量sound_dir
和img_dir
> 如下:您必须更改它们,如下所示:
其中,
resource_path()
在脚本顶部定义为:如果使用 venv,请激活虚拟环境,
如果没有,请安装 pyinstaller然而,通过:
pip3 install pyinstaller
。运行:pyi-makespec --onefile main.py 为编译和构建过程创建规范文件。
这会将文件层次结构更改为:
打开(使用编辑器)
main.spec
:在其顶部插入:
然后,将
datas=[],
行更改为datas=added_files,
有关
main.spec
上完成的操作的详细信息,请参阅 使用 PyInstaller 文档中的规范文件部分运行
pyinstaller --onefile main.spec
就这样,您可以在
中运行
,其文件夹中没有任何其他内容。您只能分发该main
任何地方的project_folder/distmain
文件。现在,它是一个真正的独立。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:
First of all, let's say you have defined your paths to
sound/
andimg/
folders into variablessound_dir
andimg_dir
as follows:You have to change them, as follows:
Where,
resource_path()
is defined in the top of your script as: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:
Open(with an edior)
main.spec
:At top of it, insert:
Then, change the line of
datas=[],
todatas=added_files,
For the details of the operations done on
main.spec
see the Using Spec Files Section in the PyInstaller DocumentationRun
pyinstaller --onefile main.spec
And that is all, you can run
main
inproject_folder/dist
from anywhere, without having anything else in its folder. You can distribute only thatmain
file. It is now, a true standalone.也许我错过了一个步骤或做错了什么,但上面的方法没有将数据文件与 PyInstaller 捆绑到一个 exe 文件中。让我分享一下我所做的步骤。
步骤:将上述方法之一写入您的 py 文件中,并导入 sys 和 os 模块。我都尝试过。最后一个是:
step: Write, pyi-makespec file.py, to the console, to create a file.spec file.
步骤:使用 Notepad++ 打开 file.spec 添加数据文件,如下所示:
步骤:我按照上述步骤操作,然后保存了spec 文件。最后打开控制台并写入,pyinstaller file.spec(在我的例子中,file=Converter-GUI)。
结论:dist文件夹中仍然存在多个文件。
注意:我使用的是Python 3.5。
编辑:最后它适用于 Jonathan Reinhart 的方法。
步骤:将以下代码添加到您的 python 文件中,并导入 sys 和 os。
步骤:调用上述函数并添加文件路径:
步骤:将调用函数的上述变量写入代码需要路径的位置。就我而言,它是:
步骤:在 python 文件的同一目录中打开控制台,编写如下代码:
步骤:保存并退出路径文件。转到包含规范和 py 文件的文件夹。再次打开控制台窗口并键入以下命令:
第 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.
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:
step: Write, pyi-makespec file.py, to the console, to create a file.spec file.
step: Open, file.spec with Notepad++ to add the data files like below:
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.
step: Add the below codes to your python file with importing sys and os.
step: Call the above function with adding the path of your file:
step: Write the above variable that call the function to where your codes need the path. In my case it's:
step: Open the console in the same directory of your python file, write the codes like below:
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:
After the 6. step your one file is ready to use.
使用 Max 和 This 的出色答案发布关于添加额外数据文件(例如图像或声音)的文章根据我自己的研究/测试,我已经找到了我认为添加此类文件的最简单方法。
如果您想查看实际示例,我的存储库位于 GitHub 上的此处。
注意:这是使用
--onefile
或-F
命令与 pyinstaller 进行编译。我的环境是接下来。
分两步解决问题
要解决这个问题,我们需要专门告诉 Pyinstaller 我们有额外的文件需要与应用程序“捆绑”。
我们还需要使用“相对”路径,以便应用程序在作为 Python 脚本或 Frozen EXE 运行时可以正常运行。
话虽如此,我们需要一个允许我们拥有相对路径的函数。使用Max Posted函数我们可以轻松解决相对路径问题。
我们将像这样使用上面的函数,以便当应用程序作为脚本或 Frozen EXE 运行时应用程序图标会显示。
下一步是我们需要指示 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'
编译为 EXE
我很懒;我不喜欢输入更多内容,我已经创建了一个只需单击即可的文件,您不必这样做,
因为没有 . spec 文件包含我们所有的编译设置& args (又名选项)我们只需将该 .spec 文件提供给 Pyinstaller 即可。
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.
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.
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 importsThe 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 setdatas=added_files
, originally it used to bedatas=[]
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'
.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.
我没有按照建议重写所有路径代码,而是更改了工作目录:
只需在代码开头添加这两行即可,其余部分保持原样。
Instead for rewriting all my path code as suggested, I changed the working directory:
Just add those two lines at the beginning of your code, you can leave the rest as is.
对已接受的答案稍作修改。
Slight modification to the accepted answer.
另一个解决方案是创建一个运行时挂钩,它将复制(或移动)您的数据(文件/文件夹)到存储可执行文件的目录。该钩子是一个简单的 python 文件,它几乎可以在执行应用程序之前执行任何操作。为了设置它,您应该使用 pyinstaller 的
--runtime-hook=my_hook.py
选项。因此,如果您的数据是 images 文件夹,则应运行以下命令:cp_images_hook.py 可能如下所示:
在每次执行之前,images 文件夹都会移动到当前目录(来自 _MEIPASS 文件夹),因此可执行文件始终可以访问它。
这样就不需要修改项目的代码。
第二种解决方案
您可以利用运行时挂钩机制并更改当前目录,根据一些开发人员的说法,这不是一个好的做法,但它工作得很好。
钩子代码可以在下面找到:
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:The cp_images_hook.py could be something like this:
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:
我在 PyInstaller 上看到的最常见的抱怨/问题是“我的代码找不到我明确包含在捆绑包中的数据文件,它在哪里?”,并且很难看出您的代码是什么/在哪里正在搜索,因为提取的代码位于临时位置,并在退出时被删除。使用 @Jonathon Reinhart 的
resource_path()
添加这段代码来查看您的 onefile 中包含的内容及其位置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()
如果您仍然尝试将与可执行文件相关的文件而不是放在临时目录中,则需要自己复制它。这就是我最终完成它的方式。
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.
我使用这个基于最大解决方案
i use this based on max solution
对于那些仍在寻找更新答案的人,请看这里:
在文档,有一个 有关访问添加的数据文件的部分。
这是它的简短而甜蜜的。
您需要
导入 pkgutil
并找到将数据文件添加到的文件夹;即元组中添加到规范文件中的第二个字符串:了解添加数据文件的位置,然后可以将其作为二进制数据读取,并根据需要对其进行解码。举个例子:
文件结构:
data_file.txt
main.py
参考资料:
pkgutil< /code>
- 内置库
__init__.py
和包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: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:
data_file.txt
main.py
References:
pkgutil
- Builtin library__init__.py
and packages最后 !基于 Luca 的解决方案,
我最终能够在 --onefile pyinstaller 中包含的目录中执行文件。
只需使用他发布的方法,您甚至可以在文件中输入整个目录名称,它仍然有效。
您需要的第二件事实际上是在 pyinstaller 中添加您想要的目录/文件。只需使用如下参数:
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:
快刀斩乱麻:
os.getcwd()
与__file__
@JonathonReinhart 强烈反对使用
os.getcwd()< /code> 在脚本中,尤其是反对更改它。这是有充分理由的。但是,正如@Guimoute 所指出的,如果您想将resource_path() 函数放入main.py 之外的某个模块中,你可能会遇到问题。这是解决这个问题的一种方法。
解决方案
我的解决方案遵循@DisappointedByUnaccountableMod的建议,但没有在main.c中定义新函数。这是实现的关键:
./main.py
:我将以下变量定义添加到主脚本文件中。
./src/helpers.py
:然后,无论您在何处使用资源,只要您想将其捆绑到
.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 theresource_path()
function into a module somewhere outside of yourmain.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.
./src/helpers.py
:Then, wherever you use a resource, which you would like to bundle into your
.exe
file, you can just callresource_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.我发现现有的答案令人困惑,并花了很长时间才找出问题所在。这是我找到的所有内容的汇编。
当我运行应用程序时,出现错误
无法执行脚本 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
(iffoo.py
is the main file). To troubleshoot this, don't run PyInstaller with--noconsole
(or editmain.spec
to changeconsole=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 folderx
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 likemain.exe.manifest
(Windows) orpython35.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)
以上都不适合我。它不允许我使用规范文件和一个文件开关运行 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!
这将为您提供当前目录路径。
This will provide you with the current directory path.