PyInstaller打包集成TensorFlow Hub的PyQt5应用为DMG后启动报错:Op with name ConstructionFails
PyInstaller打包集成TensorFlow Hub的PyQt5应用为DMG后启动报错:Op with name ConstructionFails
我之前也踩过TensorFlow + PyInstaller在MacOS打包的坑,这个AlreadyExistsError: Op with name ConstructionFails错误确实和PyInstaller打包TensorFlow的方式有关,大概率是模型资源路径错误或者TensorFlow初始化时的Op重复注册导致的。给你几个针对性的解决方案,你可以一步步排查:
1. 先修复模型资源的加载路径
你在Spec里已经把YAMNet模型目录打包进去了,但打包成App后,应用的运行根目录和开发时的相对路径完全不同,代码里如果用了硬编码的相对路径加载模型,会导致模型加载失败,间接触发TensorFlow的Op注册异常。
在你的代码里,把模型加载的路径改成从PyInstaller的临时目录获取:
import os import sys def get_yamnet_model_path(): # 打包后用sys._MEIPASS获取资源目录 if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, "yamnet-tensorflow2-yamnet-v1") # 开发环境用相对路径 else: return os.path.join(os.path.dirname(__file__), "src/selecta/yamnet-tensorflow2-yamnet-v1") # 然后用这个路径加载模型 model = tf.keras.models.load_model(get_yamnet_model_path())
2. 修改PyInstaller Spec文件优化TensorFlow打包
你的Spec文件需要针对TensorFlow的依赖做调整,避免重复打包或者破坏TensorFlow的二进制文件:
修改后的完整Spec示例:
# -*- mode: python ; coding: utf-8 -*- from PyInstaller.utils.hooks import collect_data_files, collect_submodules # 收集TensorFlow和TensorFlow Hub的所有依赖模块 tf_hidden_imports = collect_submodules('tensorflow') + collect_submodules('tensorflow_hub') a = Analysis( ['Selecta.py'], pathex=[], binaries=[], datas=[ ("src/selecta/yamnet-tensorflow2-yamnet-v1", "yamnet-tensorflow2-yamnet-v1"), # 额外打包TensorFlow的核心数据文件 *collect_data_files('tensorflow'), *collect_data_files('tensorflow_hub'), ], hiddenimports=[ "scipy._cyutility", *tf_hidden_imports, ], # 排除TensorFlow的测试模块,避免重复打包 excludes=['tensorflow.python.tests'], hookspath=[], hooksconfig={}, runtime_hooks=[], noarchive=False, optimize=0, ) pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name='Selecta', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, # 禁止UPX压缩TensorFlow的核心二进制文件(UPX会破坏TensorFlow的动态库) upx_exclude=['_pywrap_tensorflow_internal.so', 'libtensorflow_framework.dylib'], console=False, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, # 强制使用应用内部的临时目录,避免系统临时目录的权限冲突 runtime_tmpdir=None, ) coll = COLLECT( exe, a.binaries, a.datas, strip=False, upx=True, upx_exclude=['_pywrap_tensorflow_internal.so', 'libtensorflow_framework.dylib'], name='Selecta', ) app = BUNDLE( coll, name='Selecta.app', # 确保Bundle名字是标准的.app格式 icon="icon.icns", # 设置唯一的Bundle ID(替换成你的域名,比如com.yourname.Selecta) bundle_identifier="com.yourdomain.Selecta", )
3. 在代码里调整TensorFlow的初始化逻辑
TensorFlow在GUI应用中的初始化顺序很重要,建议在应用入口(比如if __name__ == "__main__":的最开头)添加TensorFlow的初始化代码,避免Op重复注册:
import tensorflow as tf from tensorflow.keras.backend import clear_session if __name__ == "__main__": # 先清空TensorFlow会话,避免残留的Op注册 clear_session() # 如果不需要GPU,直接禁用(MacOS的GPU支持经常出问题) tf.config.set_visible_devices([], 'GPU') # 然后再初始化PyQt5应用 from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) # 剩下的应用逻辑... your_main_window = MainWindow() your_main_window.show() sys.exit(app.exec_())
4. 正确打包DMG的姿势
用create-dmg的时候,要确保App包的权限正确,并且打包参数不要破坏App结构:
# 1. 先给打包好的App设置正确的权限 chmod -R 755 dist/Selecta.app # 2. 用标准参数打包DMG create-dmg \ --volname "Selecta Installer" \ --window-pos 200 120 \ --window-size 800 400 \ --icon-size 100 \ --icon "Selecta.app" 200 190 \ --hide-extension "Selecta.app" \ --app-drop-link 600 185 \ "Selecta.dmg" \ "dist/"
如果还是有问题,可以尝试:
- 暂时禁用UPX压缩(把Spec里的
upx=True改成upx=False),看看是不是UPX破坏了TensorFlow的二进制文件 - 检查MacOS的隐私权限(比如你的App需要访问麦克风的话,要在
entitlements_file里添加麦克风权限声明)
内容来源于stack exchange
相关产品推荐
相关产品推荐

