首页 > 解决方案 > PyInstaller ModuleNotFoundError --paths 标志似乎不起作用

问题描述

我使用 Tkinter 构建了一个简单的 GUI,我想将其冻结为独立的可执行文件。我在 conda 环境中执行此操作。使用 OSX 10.15.7、python 3.7、PyInstaller 4.5.1 和 conda 4.10.0。文件夹结构如下所示(简化):

 - ImSep_files
  | - ai4eutils
  | - ImSep
  |  | - ImSep_GUI.py
  | - cameratraps
     | - detection
        | - run_tf_detector.py

该脚本调用 ai4eutils 和 cameratraps 文件夹中的其他脚本。如果我创建一个 conda env,将 设置为包含andPYTHONPATH的路径,然后运行,就没有问题。GUI 打开并完美运行。但是,如果我执行完全相同但运行而不是运行,它会创建一个 exe,它会打开 GUI,但在按下按钮时会引发错误。ai4eutilscameratrapspython ImSep_GUI.pypyinstallerpython

  File "/Users/peter/Applications/ImSep_files/cameratraps/detection/run_tf_detector_batch.py", line 56, in <module>
    from detection.run_tf_detector import ImagePathUtils, TFDetector
ModuleNotFoundError: No module named 'detection.run_tf_detector'

这意味着pyinstaller找不到run_tf_detector.py文件。我尝试添加如下--paths标志:

pyinstaller --onefile --windowed --name='ImSep' --icon='imgs/logo_small_bg.icns' --paths=/Users/peter/Applications/ImSep_files --paths=/Users/peter/Applications/ImSep_files/ai4eutils --paths=/Users/peter/Applications/ImSep_files/cameratraps --paths=/Users/peter/Applications/ImSep_files/cameratraps/detection ImSep_GUI.py

我知道有很多关于这种类型或错误的主题。我尝试了许多潜在的解决方案,但似乎都没有奏效。我尝试了以下方法:

仅供参考,这就是我创建环境和运行的方式pyinstaller

conda create --name imsepcondaenv python=3.7 -y
conda activate imsepcondaenv
pip install tensorflow==1.14 pillow==8.4.0 humanfriendly==10.0 matplotlib==3.4.3 tqdm==4.62.3 jsonpickle==2.0.0 statistics==1.0.3.5 requests==2.26.0
conda install -c conda-forge pyinstaller -y
cd ~/Applications/ImSep_files
export PYTHONPATH="$PYTHONPATH:$PWD/ai4eutils:$PWD/cameratraps"
cd ImSep
pyinstaller --onefile --windowed --name='ImSep' --icon='imgs/logo_small_bg.icns' --paths=/Users/peter/Applications/ImSep_files --paths=/Users/peter/Applications/ImSep_files/ai4eutils --paths=/Users/peter/Applications/ImSep_files/cameratraps --paths=/Users/peter/Applications/ImSep_files/cameratraps/detection ImSep_GUI.py

有谁知道我做错了什么?

PS:对于 OSX 和 UNIX 用户,可以获得可重现的示例:

mkdir ImSep_files
cd ImSep_files
git clone https://github.com/Microsoft/cameratraps -b tf1-compat
git clone https://github.com/Microsoft/ai4eutils
git clone https://github.com/PetervanLunteren/ImSep.git
curl --output md_v4.1.0.pb https://lilablobssc.blob.core.windows.net/models/camera_traps/megadetector/md_v4.1.0/md_v4.1.0.pb

标签: python-3.xpyinstallermodulenotfounderror

解决方案


PYTHONPATH几乎总是局部最小值。根据我的经验,从长远来看,它只会使事情复杂化。我建议将第 1 步PYTHONPATH从您的工作流程中删除,并了解 python 软件包和可编辑的 intsalls。从长远来看,这将使开发变得更加容易。

PYTHONPATH基本上是作为一种让“脚本”在不实际安装包的情况下访问其他模块的方式开始的。这在 virtualenv 和 conda 之前的糟糕日子里更有意义,但现在使用包结构更容易,更有条理。

尝试像典型的可安装 python 库一样构建您的项目。例如

.
├── .git
├── ImSep_files
│  ├── ai4eutils
│  ├── cameratraps
│  │  └── detection
│  │     └── run_tf_detector.py
│  └── ImSep
│     └── ImSep_GUI.py
└── setup.py

确保你可以pip install .从你的根目录。您应该有一些您从中导入的顶级包名称(在这种情况下,我任意选择ImgSep_Files了您的库名称,但它可以是任何名称)。然后你应该能够总是使用绝对或相对包语法导入,比如

from .detection.run_tf_detector import ImagePathUtils, TFDetector

最终的考验是你能不能跑python -m ImSep_files.cameratraps.detection.run_tf_detector使用PYTHONPATH. 这意味着您的导入结构正确,pyinstaller 应该可以毫无问题地处理您的依赖项。

更新:这是一个带有setup.py. 我选择了 setup.py,尽管这有点老派,而且事情正在朝着这个方向发展,pyproject.toml因为有更多关于这种风格的文档:

from setuptools import setup, find_packages

setup(
    name="my_package",
    description="An example setup.py",
    license="MIT",
    packages=find_packages(),
    python_requires=">=3.7",
    zip_safe=False,
    install_requires=[
        "tensorflow",
    ],
    classifiers=[
        "Programming Language :: Python :: 3.7",
    ],
    entry_points={
        "console_scripts": [
            "run_tf_detector=my_package.scripts.run_tf_detector:main",
            "imsep_gui=my_package.gui.gui:main",
        ]
    },
)

然后我有一个这样的布局:

.
└── my_project_name
   ├── .git
   ├── my_package
   │  ├── gui
   │  │  ├── gui.py
   │  │  └── gui_utils.py
   │  ├── scripts
   │  │  └── run_tf_detector.py
   │  └── detection
   │     └── tf_detector.py
   ├── README.md
   ├── setup.py
   └── tests
      └── test_tf_detector.py

my_project_name是我的“回购根”。my_package是我的包裹的名称。我会像from my_package.detection.tf_detector import TFDetector. 在这种情况下,我会将所有的类和逻辑放在 中tf_detector.py,然后run_tf_detector.py基本上就是:

import sys
from my_package.detection.tf_detector import TFDetector


def main(args=None):
    args = args or sys.argv
    detector = TFDetector()
    detector.detect(args)

if __name__ == __main__:
    main()

GUI 遵循一个简单的模式,gui.py包含启动 gui 的入口点。这种组织方式将您的功能代码与作为脚本运行的具体细节分开。例如,它可以很容易地让检测器作为 CLI 脚本运行,或者作为 GUI 的一部分,或者作为可以导入的库。

入口点用于告诉安装程序“这是您运行的东西或插件”。更多信息


推荐阅读