我使用Tkinter构建了一个简单的GUI,我想将其冻结为独立的可执行文件。我是在一个隐蔽的环境中做这件事。使用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和camerataps文件夹中的其他脚本。如果创建conda环境,将PYTHONPATH
设置为包含ai4eutils
和cameratraps
的路径,并运行python ImSep_GUI.py
,则没有问题。GUI打开并正常运行。然而,如果我做完全相同的,但运行pyinstaller
而不是python
,它会创建一个打开GUI的exe,但在按下按钮时抛出错误。
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
标志,如hest所建议的。如果尝试不同的版本:--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
的顶部。 - 将
pyinstaller
降级为3.1,如这里所建议的那样。 - 在
hooks
文件夹中创建hook.py
和detection.run_tf_detector
,并将其添加为--additional-hooks-dir=hooks
,如Legorooj所建议的。 - 按照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
几乎总是局部极小值。根据我的经验,从长远来看,这只会让事情变得更复杂。我建议第一步是从工作流程中删除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的一部分运行,或者作为可以导入的库运行。
入口点用来告诉安装程序"这是你要运行的东西还是一个插件"。更多信息