如何阻止Matplotlib在PyInstaller打包的MacOS应用首次运行时构建字体缓存?
解决PyInstaller打包MacOS应用后Matplotlib字体缓存构建问题
Matplotlib首次运行时扫描系统字体并构建缓存的行为,哪怕指定了自定义字体也会触发,要彻底避免这个问题,需要从阻止系统字体扫描和预生成专属字体缓存两个方向入手,以下是具体可行的方案:
方案一:预生成字体缓存并打包(最可靠)
提前在开发环境生成仅包含你捆绑的DejaVuSans.ttf的字体缓存,然后将缓存文件打包进应用,让Matplotlib直接加载预生成的缓存,跳过扫描步骤:
- 生成专属字体缓存
创建一个临时脚本(比如generate_font_cache.py),运行它生成仅包含目标字体的缓存:
import matplotlib from matplotlib import font_manager import os # 清空默认字体配置,只加载我们的字体 font_manager.fontManager.ttflist.clear() font_manager.fontManager._ttf_cache.clear() font_path = "resources/DejaVuSans.ttf" font_manager.fontManager.addfont(font_path) font_name = font_manager.FontProperties(fname=font_path).get_name() # 设置默认字体参数 matplotlib.rcParams.update({ 'font.family': font_name, 'font.sans-serif': [font_name], 'font.serif': [font_name], 'font.monospace': [font_name] }) # 强制生成并保存缓存 cache_dir = "./matplotlib_cache" os.makedirs(cache_dir, exist_ok=True) font_manager.fontManager.save(os.path.join(cache_dir, "fontlist-v330.json"))
注意:
fontlist-v330.json中的版本号(v330)可能随Matplotlib版本变化,运行脚本后查看生成的文件名,确保后续引用正确。
- 打包缓存文件到应用
- 如果使用
.spec文件打包,在datas字段中添加缓存和字体文件的路径映射:
datas=[ ('resources/DejaVuSans.ttf', 'resources'), ('matplotlib_cache/fontlist-v330.json', 'matplotlib_cache') ],
- 如果用命令行打包,使用
--add-data参数:
pyinstaller --add-data "resources/DejaVuSans.ttf:resources" --add-data "matplotlib_cache/fontlist-v330.json:matplotlib_cache" your_main_script.py
- 应用启动时加载预生成缓存
在你的主脚本开头,先设置Matplotlib加载预打包的缓存,再导入pyplot:
import matplotlib import os from matplotlib import font_manager # 获取应用所在目录 app_dir = os.path.dirname(os.path.abspath(__file__)) # 指定缓存路径并加载预生成的缓存 cache_file = os.path.join(app_dir, "matplotlib_cache/fontlist-v330.json") matplotlib.rcParams['font.cache_path'] = os.path.dirname(cache_file) font_manager.fontManager = font_manager._load_fontmanager(cache_file) # 再次确认字体配置(可选,但能避免意外) font_path = os.path.join(app_dir, "resources/DejaVuSans.ttf") font_name = font_manager.FontProperties(fname=font_path).get_name() matplotlib.rcParams.update({ 'font.family': font_name, 'font.sans-serif': [font_name], 'font.serif': [font_name], 'font.monospace': [font_name] }) # 最后导入pyplot import matplotlib.pyplot as plt
方案二:直接阻止Matplotlib扫描系统字体
通过覆盖Matplotlib内部的字体扫描函数,让它完全忽略系统字体,只关注你捆绑的字体:
import matplotlib from matplotlib import font_manager # 清空现有字体列表 font_manager.fontManager.ttflist.clear() font_manager.fontManager._ttf_cache.clear() # 阻止Matplotlib读取系统fontconfig和扫描系统字体 font_manager._get_fontconfig_fonts = lambda: [] font_manager.findSystemFonts = lambda paths=None, fontext='ttf': [] # 添加并设置我们的捆绑字体 font_path = "resources/DejaVuSans.ttf" font_manager.fontManager.addfont(font_path) font_name = font_manager.FontProperties(fname=font_path).get_name() matplotlib.rcParams.update({ 'font.family': font_name, 'font.sans-serif': [font_name], 'font.serif': [font_name], 'font.monospace': [font_name] }) # 导入pyplot import matplotlib.pyplot as plt
注意:这种方法依赖Matplotlib的内部函数名,后续版本更新可能失效,建议优先使用方案一。
内容的提问来源于stack exchange,提问作者Martín Fixman
相关产品推荐
相关产品推荐

