如何使用Python3在PyInstaller中设置相对路径来创建可移植的.exe版本?

2022-03-23 00:00:00 python pyinstaller exe build

问题描述

我到处都找过了,一个相当琐碎的问题没有得到明确的答复。

我在Windows7上的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文件的绝对路径,应用程序会编译并且没有运行时错误。然而,如果我在脚本中使用相对路径,应用程序会编译,但会给出一个运行时错误,即.txt文件(即file1.txt)在/build(或/dist,我在这里可能是错误的)目录(显然不在那里)中找不到。

当然,硬编码绝对路径是一种糟糕的做法,尤其是在讨论到另一台机器的可移植性时,而且还会使应用程序跨平台。我知道构建过程可能依赖于sys._MEIPASS,但我不知道如何在我的上下文中确切地使用它。

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


解决方案

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

当您执行该文件时,这些文件将被"解压缩"到临时文件位置。在Windows上,这通常是C:Users<You>AppDataLocalTempMEIxxx

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

C:\Users\%username%\PycharmProjects\%project_folder%\%project_folder% xt_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> xt_filesfile1.txt,但在捆绑的应用程序中,它们将是<MEIPASS directory>file1.txt。您可能希望通过执行以下操作来保持路径的"相对"部分相同

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

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

  1. 还要考虑如果使用规范文件生成,请删除COLLECT部分以生成一个捆绑的可执行文件。

相关文章