PyInstaller打包Vispy应用本地报FT_Exception,其他机器正常
问题:Vispy项目PyInstaller打包后本地运行报FT_Exception错误
现象
- 直接运行Python脚本(
.py)时一切正常。 - 使用PyInstaller打包为
.exe后,程序在其他计算机上运行正常,但本地抛出错误:freetype.ft_errors.FT_Exception: FT_Exception: (cannot open resource)
打包配置文件(ls2.spec)
# -*- mode: python -*- # ls2.spec import sys import os # 原代码缺少os导入,需补上 from PyInstaller.compat import is_win, is_darwin, is_linux from PyInstaller.utils.hooks import collect_submodules,collect_dynamic_libs import vispy.glsl import vispy.io import freetype block_cipher = None data_files = [ (os.path.dirname(vispy.glsl.__file__), os.path.join("vispy", "glsl")), (os.path.join(os.path.dirname(vispy.io.__file__), "_data"), os.path.join("vispy", "io", "_data")), (os.path.dirname(vispy.util.__file__), os.path.join("vispy", "util")), (os.path.dirname(freetype.__file__), os.path.join("freetype")), ] hidden_imports = [ "vispy.ext._bundled.six", "vispy.app.backends._wx", "vispy.app.backends._pyqt5", "freetype" ] if is_win: hidden_imports += collect_submodules("encodings") a = Analysis( ['ls2.py'], pathex=[], binaries=[], datas=data_files, hiddenimports=hidden_imports, hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name='ls2', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=True, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, ) coll = COLLECT( exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='ls2', )
报错堆栈信息
WARNING: Traceback (most recent call last): File "ls.py", line 229, in <module> File "vispy\app\backends\_qt.py", line 626, in event File "vispy\app\backends\_qt.py", line 626, in event File "vispy\app\backends\_qt.py", line 928, in paintGL File "vispy\util\event.py", line 453, in __call__ self._invoke_callback(cb, event) File "vispy\util\event.py", line 471, in _invoke_callback _handle_exception(self.ignore_callback_errors, << caught exception here: >> File "vispy\util\event.py", line 469, in _invoke_callback cb(event) File "vispy\scene\canvas.py", line 219, in on_draw File "vispy\scene\canvas.py", line 278, in _draw_scene File "vispy\scene\canvas.py", line 316, in draw_visual File "vispy\scene\visuals.py", line 106, in draw File "vispy\visuals\visual.py", line 668, in draw File "vispy\visuals\visual.py", line 668, in draw File "vispy\visuals\visual.py", line 505, in draw File "vispy\visuals\text\text.py", line 533, in _prepare_draw File "vispy\visuals\text\text.py", line 534, in <listcomp> File "vispy\visuals\text\text.py", line 264, in _text_to_vbo File "vispy\visuals\text\text.py", line 81, in __getitem__ File "vispy\visuals\text\text.py", line 95, in _load_char File "vispy\util\fonts\_freetype.py", line 50, in _load_glyph face = _load_font(f['face'], f['bold'], f['italic']) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "vispy\util\fonts\_freetype.py", line 37, in _load_font font = Face(fname) ^^^^^^^^^^^ File "freetype\__init__.py", line 1192, in __init__ raise FT_Exception(error) freetype.ft_errors.FT_Exception: FT_Exception: (cannot open resource)
原因分析
- 本地系统字体异常:打包后的exe优先尝试加载本地系统字体,但你的电脑存在字体文件损坏、权限不足或路径识别问题,其他机器的系统字体环境无异常。
- 打包资源不完整:虽然spec包含了
vispy.io._data,但Vispy默认字体的路径在打包后未被正确识别,本地运行时无法回退到打包的字体资源。 - 动态库版本冲突:原spec仅收集了freetype的Python模块,未包含其依赖的动态链接库,本地运行时与系统已安装的freetype库版本冲突;其他机器未预安装freetype,只能使用打包版本,反而正常。
解决方案
1. 修复spec文件,完善资源打包
- 补上
os模块导入,解决路径解析报错。 - 用
collect_dynamic_libs收集freetype的动态库,添加到binaries中。 - 明确指定Vispy字体文件路径,确保被打包:
修改后的spec关键部分:
import os # 新增 # 收集freetype动态库 binaries = collect_dynamic_libs('freetype') data_files = [ (os.path.dirname(vispy.glsl.__file__), os.path.join("vispy", "glsl")), (os.path.join(os.path.dirname(vispy.io.__file__), "_data"), os.path.join("vispy", "io", "_data")), (os.path.dirname(vispy.util.__file__), os.path.join("vispy", "util")), # 明确收集字体文件 (os.path.join(os.path.dirname(vispy.io.__file__), "_data", "fonts"), os.path.join("vispy", "io", "_data", "fonts")), ] a = Analysis( ['ls2.py'], pathex=[], binaries=binaries, # 加入动态库 datas=data_files, # 其他参数不变 )
2. 代码中显式指定打包后的字体
在项目代码(ls2.py)中添加逻辑,让Vispy优先使用打包后的默认字体:
import sys import os from vispy import visuals def get_packed_font_path(): # PyInstaller打包后,资源文件存放在sys._MEIPASS目录 if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, 'vispy', 'io', '_data', 'fonts', 'OpenSans-Regular.ttf') # 未打包时使用默认路径 else: from vispy.io import load_data_file return load_data_file('fonts/OpenSans-Regular.ttf') # 设置全局默认字体 visuals.TextVisual.set_default_font(get_packed_font_path())
3. 排除UPX对freetype动态库的压缩
UPX压缩可能损坏freetype动态库,在spec的EXE和COLLECT部分添加排除:
exe = EXE( # 其他参数不变 upx_exclude=['freetype.dll'], # Windows下的freetype动态库文件名,按需调整 ) coll = COLLECT( # 其他参数不变 upx_exclude=['freetype.dll'], )
4. 临时排查本地系统字体问题
- 临时重命名系统字体目录(如Windows下的
C:\Windows\Fonts,注意备份),测试是否为系统字体导致的冲突。 - 检查是否存在
FREETYPE_PATH环境变量,如有则临时删除后再运行exe。
内容的提问来源于stack exchange,提问作者eehan
相关产品推荐
相关产品推荐

