【问题标题】:How to set up relative paths to make a portable .exe build in PyInstaller with Python 3?如何设置相对路径以使用 Python 3 在 PyInstaller 中构建可移植的 .exe?
【发布时间】:2020-07-11 05:21:07
【问题描述】:

我到处查找,但对一个相当琐碎的问题没有明确的答复。

我在 Windows 7 上的 PyCharm 中有一个 Python 项目,其中包含多个 .py 文件(通过“from %package_name%.%script_name% import %class_name%”连接)和项目内的一个文件夹,其中包含两个简单的文本文件。我已将 PyInstaller 3.6 安装到项目的 venv 中,并将其用作外部工具,指向 .spec 文件。到目前为止,一切都很好。 .spec文件如下:

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None


a = Analysis(['C:\\Users\\%username%\\PycharmProjects\\%project_folder%\\%project_folder%\\main.py'],
             pathex=['C:\\Users\\%username%\\PycharmProjects\\%project_folder%\\%project_folder%'],
             binaries=[],
             datas=[('txt_files\\file1.txt', '.'), ('txt_files\\file2.txt', '.')],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
a.datas += [
    ("C:\\Users\\%username%\\PycharmProjects\\%project_folder%\\%project_folder%\\txt_files\\file1.txt","txt_files\\file1.txt","DATA"),
    ("C:\\Users\\%username%\\PycharmProjects\\%project_folder%\\%project_folder%\\txt_files\\file2.txt","txt_files\\file2.txt","DATA"),
]
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='%project_name%',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='%project_name%')

问题在于,如果我在脚本本身中对捆绑的.txt 文件的绝对路径进行硬编码,则应用程序可以编译并且没有运行时错误。但是,如果我在脚本中使用相对路径,则应用程序会编译,但会出现运行时错误,即在/build(或/dist)内部找不到.txt 文件(即file1.txt),我这里可能是错误的)目录(显然不存在)。

当然,对绝对路径进行硬编码是一种不好的做法,尤其是在谈论到另一台机器的可移植性以及使应用程序跨平台时。我知道构建过程可能取决于sys._MEIPASS,但我不知道如何在我的上下文中使用它。

我应该在哪个脚本(main、.spec 或其他?)中放置使用sys._MEIPASS 获取捆绑文件的绝对路径的部分?这个代码部分在 Python 3.7 上应该是什么样子?我已经看到了不同的答案(即this one)并且已经尝试过,但在我的情况下似乎都没有。

【问题讨论】:

  • 您是捆绑到一个可执行文件(带有--onedir)还是一个目录?
  • 是的,我想生成一个捆绑的可执行文件,但是当我通过--onedir 选项时,我收到了错误Security-Alert: try to store file outside of dist-directory. Aborting.。现在,为了在代码中获取在venv 中工作的路径,我使用file_path = os.path.abspath(os.path.join(os.getcwd(), rel_path)),其中rel_path 是txt_files\\file1.txt,它在PyCharm 中工作,但是,当运行应用程序本身时,它会生成一个@ 987654344@ 在C:\\Users\\%username%\\PycharmProjects\\%project_folder%\\%project_folder%\\dist\\%project_folder%\\txt_files\\file1.txt。

标签: python build exe pyinstaller


【解决方案1】:

使用--onefile 将所有数据捆绑到.exe 文件中。

当您执行文件时,这些文件会“解压”到一个临时文件位置。在 Windows 上,这通常是 C:\Users\<You>\AppData\Local\Temp\MEIxxx。

因此,当您开发脚本时,数据文件(本示例中为您的文本文件)将位于

C:\\Users\\%username%\\PycharmProjects\\%project_folder%\\%project_folder%\txt_files\

但是当应用程序被编译时,它们会被解压到上面提到的临时目录中。所以你需要一种方法来告诉脚本你是在开发,还是已经编译。在这里你可以使用the 'frozen' flag (see the docs here)

我之前使用过的一种方法是创建一个这样的实用函数

def resolve_path(path):
    if getattr(sys, "frozen", False):
        # If the 'frozen' flag is set, we are in bundled-app mode!
        resolved_path = os.path.abspath(os.path.join(sys._MEIPASS, path))
    else:
        # Normal development mode. Use os.getcwd() or __file__ as appropriate in your case...
        resolved_path = os.path.abspath(os.path.join(os.getcwd(), path))

    return resolved_path

然后,只要您想在脚本中使用路径,例如访问您的文本文件,您就可以这样做

with open(resolve_path("txt_files/file1.txt"), "r") as txt:
    ...

无论您处于哪种模式,它都应该解析正确的路径。

.spec 文件的注释

  1. 您不必单独指定所有文本文件。您当然可以,而且您可能有充分的理由这样做,这很好。但你可以这样做

datas=[('txt_files', '.')]

它将txt_files 目录的内容放在你的包的根目录中。但是请注意这一点,因为现在文本文件的路径将是 <dev directory>\txt_files\file1.txt,但在捆绑的应用程序中,它们将是 <MEIPASS directory>\file1.txt。您可能希望通过这样做来保持路径的“相对”部分相同

datas=[('txt_files', 'txt_files')]

这将反映您的开发文件夹和捆绑应用程序之间的文件结构。

  1. 还要考虑如果您使用规范文件进行构建,请删除 COLLECT 部分,以便生成捆绑在一起的 onefile 可执行文件。

【讨论】:

  • 好的,这就是我所做的 - 我在读取文件的脚本中安装了代码,从规范中删除了 COLLECT,将 exclude_binaries 设置为 False(这样捆绑的.exe 文件应出现在dist 目录中,就像它应该出现的那样),并且还将a.datas 添加到规范的EXE 部分,但是现在在Windows 控制台中运行应用程序时出现错误@987654341 @,然后是一些难以理解的符号,不管我怎么改chcp(可能是One or more arguments are invalid)。
  • 如何将 Python 添加到 _MEIPASS 临时目录中?
  • 如果你还没有,你应该在 EXE 步骤中添加a.binaries。顺便说一句 - 在“一个目录”模式下构建时,调试这些问题要容易得多。通常一个好的工作流程是让它像这样工作,然后在确定一切正常后切换到使用--onedir。
  • 好吧,我将a.binaries 添加到EXE 并保留--onedir 参数不变。现在就来测试一下。
  • 最后,您的捆绑镜像datas=[('txt_files', 'txt_files')] 的解决方案奏效了。非常感谢! (不过,不要忘记在datas 中的第一个“txt_files”中添加结束标记)
【解决方案2】:

我有以下FS

  • GitRepoForMySomeProject
    • MySomeProject
      • main.py
    • MyGitSubmodule
    • ExeSettings.spec
    • ExeVersion.py
    • ExeMySomeProject.bat ("pyinstaller ExeSettings.spec")

在 main.py 中使用了一些子模块 放入子文件夹“MyGitSubmodule/ReportGenerator.py”

from MyGitSubmodule import ReportGenerator as _report_

这是我的 ExeSettings.spec 文件,相对路径 pathex=['../MySomeProject']

# -*- mode: python -*-

block_cipher = None


a = Analysis(['MySomeProject/main.py'],
             pathex=['../MySomeProject'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          name='MySomeProject',
          debug=False,
          strip=False,
          upx=True,
          console=False,
          icon='my.ico',
          version='ExeVersion.py')

如果忘记添加此路径,我们将有

ModuleNotFoundError: 没有名为“MyGitSubmodule”的模块

【讨论】:

    猜你喜欢
    • 2021-07-25
    • 1970-01-01
    • 1970-01-01
    • 2016-02-12
    • 1970-01-01
    • 2013-06-10
    • 2013-06-10
    • 1970-01-01
    • 2013-11-02
    相关资源
    最近更新 更多