python-3.x - 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,但在按下按钮时会引发错误。ai4eutils
cameratraps
python ImSep_GUI.py
pyinstaller
python
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
我知道有很多关于这种类型或错误的主题。我尝试了许多潜在的解决方案,但似乎都没有奏效。我尝试了以下方法:
- 使用
--hidden-import
标志,如HHest在此处建议的那样。如果尝试不同的版本:--hidden-import detection.run_tf_detector
,--hidden-import cameratraps.detection.run_tf_detector
,--hidden-import cameratraps.detection
, 等。 - 按照user1251007
hiddenimports=[],
的建议,使用上述路径调整行。 - 添加
sys.path.append(path/to/run_tf_detector.py)
到顶部ImSep_GUI.py
。 - 按照Fivef的建议,降级
pyinstaller
到 3.1 。 - 按照Legorooj的建议,在文件夹中创建一个
hook.py
并将其添加为。detection.run_tf_detector
hooks
--additional-hooks-dir=hooks
- 按照Ken4scholars的建议,将所需的模块作为规范文件中的数据加载。
- 按照Wayne Zhang的建议,复制到
run_tf_detector.py
同级别的文件夹中。ImSep.exe
pyinstaller
从父目录调用,如all 或 None 所建议的。- 按照Habeeb Rahman K T的建议,安装在
pyinstaller
存在的同一目录中。ImSep_GUI.py
- 按照管道管道的建议,使用
pyinstaller
而conda-forge
不是安装。pip
仅供参考,这就是我创建环境和运行的方式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
解决方案
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 的一部分,或者作为可以导入的库。
推荐阅读
- c++ - C++ 递归函数
- angular - 服务收到回调后更新组件视图
- postgrest - 使用 postgREST API 进行 OR 操作
- flutter - 飞镖 | 如何从类构造函数返回不同的对象
- javascript - 尝试打开带有完整结果的 autocomplete.js 弹出窗口
- javascript - 在 html 网站中添加一个按钮以更改为暗模式
- python - Python pandas:使用随机字符串更改每一行的值
- c++ - C ++模板函数检查向量是否包含值?
- assembly - 系统在 QEMU 上启动,但在真实硬件上出现恐慌(重新启动)
- c++ - 我可以为多个向量使用一个分配器实例吗?