新闻详情

新闻详情

首页 / 资讯中心 / 详情

Matplotlib字体设置:中英文混排各用各的字体,告别豆腐块

发布时间:2026/10/2 15:12:06来源:尧图网络
Matplotlib字体设置:中英文混排各用各的字体,告别豆腐块
每次用matplotlib出图最让人血压飙升的往往不是数据算错而是标题里的中文变成一排豆腐块。你以为加上SimHei就完事了结果数字、英文也跟着变成黑体在论文插图里怎么看怎么别扭。今天这篇就是想一次性说清楚怎么让matplotlib里中英文各用各的字体而不是粗暴地全图统一。先明确一下这里的核心诉求。第一层最简单——让中文字符正常显示不要乱码第二层才是真正的痛点——同一张图里中文用中文字体英文和数字用英文字体比如中文用黑体/宋体英文用Times New Roman。后面这层需求在数据图表、论文配图、量化分析报告里非常常见也是网上一堆教程没讲透的地方。这篇文章会从matplotlib的字体机制讲起把全局配置、局部指定、新版字体回退、字符拆分这几条路线全部拆开最后给你一套可以直接抄走的代码。1. 乱码的根源先理解matplotlib的字体选择机制1.1 默认字体为什么渲染不出中文matplotlib自带的默认字体是DejaVu Sans这套字体只覆盖拉丁字母、数字和常用符号完全没有CJK中日韩统一表意文字字形。当文本里出现中文字符时渲染器按字体描述去逐个找字形glyph找不到就画一个空心方块既不报错也不提醒这就是豆腐块乱码的真正来源。听上去很简单但这里藏着一个很多人会踩的误区matplotlib不是一个会自动识别系统语言并切换字体的智能应用它的一切字体行为都是按你给的字体列表逐字符查字形查不到就用占位符。所以代码不报错、图也能正常保存——你盯着屏幕半天才发现标题全乱了排查方向却完全跑偏。遇到这种情况第一件事不是看异常堆栈而是检查当前使用的字体列表和实际渲染结果。1.2 font_manager 和字体注册机制matplotlib的字体管理核心是font_manager模块。它的工作流程是启动时扫描系统字体目录把扫描结果写进一个缓存文件之后所有字体查询都走这个缓存。Windows扫描的是系统Fonts目录Linux扫描/usr/share/fonts这一类路径macOS扫描系统字体目录。这也是为什么你明明刚装好一个中文字体重启Python后matplotlib却依然不认——因为它用的还是旧缓存。缓存文件的位置一般在~/.cache/matplotlib/fontlist-*.jsonLinux/macOSWindows上在%USERPROFILE%\.matplotlib目录里。清掉缓存重启Python是最直接的刷新方式。另外还有一个更灵活的手段font_manager.fontManager.addfont()可以动态加载一个字体文件不需要清理缓存当前进程立即生效后面会详细演示。1.3 字体设置的三层入口matplotlib的字体设置分为三个粒度很多人搞混就是因为没分清这三层全局设置plt.rcParams[font.family]、plt.rcParams[font.sans-serif]影响整张图所有文本。对象级参数ax.set_title(fontproperties...)、ax.set_xlabel(fontproperties...)、ax.legend(prop...)、ax.text(fontproperties...)只影响当前这个文本元素。FontProperties对象一个封装了字体族、字号、粗细、样式等属性的独立对象可以创建一次、到处复用。这三层之间的优先级是对象级参数覆盖全局设置同一元素上显式传入的字体描述决定最终渲染效果。理解了这层关系你就会明白中英文不同字体本质上不是一个全局开关而是一个字体匹配顺序和渲染粒度问题——也正因为如此新版matplotlib的字体回退font fallback机制才值得单独开一大章来讲。2. 全局设置先让整张图不崩2.1 标准rcParams三件套最基础的配置我建议每个用matplotlib画图的人都无条件写进脚本开头import matplotlib.pyplot as plt plt.rcParams[font.family] sans-serif plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, Noto Sans CJK SC, DejaVu Sans] plt.rcParams[axes.unicode_minus] False第一行把默认字体族设为sans-serif第二行指定这个族内的字体候选顺序第三行关闭unicode负号。为什么第三行特别重要因为很多中文字体里没有U2212这个Unicode减号字形坐标轴一旦出现负值负号就会渲染成方块。设为False之后负号用普通连字符视觉差异极小而安全系数极高。第二行的字体顺序也有讲究中文字体放前面DejaVu Sans放最后垫底。在matplotlib 3.6之前的版本渲染时会按列表顺序找第一个可用的字体然后一股脑用这个字体渲染所有字符不会逐个检查字形覆盖范围中文字体放前面能保证中文优先使用中文字形英文和数字则跟随中文字体自带的拉丁字形。如果你把DejaVu Sans放在第一位中文直接就崩了。2.2 不同平台的中文字体选择不同操作系统能直接用的中文字体差别很大这里按平台列一下实际可用的候选WindowsSimHei黑体、SimSun宋体、Microsoft YaHei微软雅黑、KaiTi楷体基本开箱即用。macOSPingFang SC苹方、STHeiti、Hiragino Sans GB注册之后要打一下字体列表确认family名因为macOS的字体名有时带小数点前缀。Linux服务器通常一个中文字体都没有需要先安装。Debian/Ubuntu下执行sudo apt install fonts-noto-cjk装完重启Python并清缓存。字体family名和文件名不是一回事。SimHei.ttf的family名就是SimHei但NotoSansCJK-Regular.ttc里可能包含一串family名比如Noto Sans CJK SC、Noto Sans CJK JP、Noto Sans CJK KR等。注册完一定要打印ttflist确认别想当然地用文件名。2.3 验证字体是否真的被识别写一个体检函数是排查字体问题最有效的一步from matplotlib import font_manager cjk_names sorted({ f.name for f in font_manager.fontManager.ttflist if any(kw in f.name for kw in (CJK, Hei, YaHei, Song, Kai, PingFang, WenQuanYi)) }) print(cjk_names)如果列表为空说明系统里干净得连一个中文字体都没有先去装字体。如果列表里有目标字体但仍乱码那问题基本不在字体注册而在rcParams配置或缓存。addfont是手动注册第三方字体的唯一快捷通道比如你下载了一个阿里普惠体或者思源黑体放着ttf不用太浪费font_manager.fontManager.addfont(/path/to/AlibabaPuHuiTi-3-55-Regular.ttf)addfont是动态加载进当前进程内存的不会写缓存文件。也就是说脚本下次重新运行时还得再执行一次addfont除非这个字体文件本身就位于系统扫描目录并且缓存已被刷新。很多人踩过这个坑以为addfont是永久生效。3. 中英文设置不同字体的核心方案3.1 不同图形元素分别指定字体如果中英文出现在不同的图形元素里比如横轴标签是中文、图例内容全是英文最简单可靠的做法是逐元素指定互不干扰from matplotlib.font_manager import FontProperties cn_font FontProperties(familyNoto Sans CJK SC, size12) en_font FontProperties(familyTimes New Roman, size12) ax.set_xlabel(采样时间 (min), fontpropertiescn_font) ax.set_ylabel(Response Value, fontpropertiesen_font) ax.legend(propen_font)这里有一个高频翻车点图例的字体参数名是prop而不是fontproperties。这是matplotlib历史沿袭的老接口写错直接TypeError。标题、轴标签、刻度标签、文本注释、图例各自支持独立的字体参数局部指定可以完全覆盖全局设置。这个方法理解门槛最低但它有一个硬伤——如果同一个文本字符串里既有中文又有英文局部指定一套字体就只能二选一。3.2 同一文本对象的中英文混排使用新版字体回退真正需要中文用宋体、英文用Times的场景往往是同一个字符串里混排比如图例里的Mean Value 平均值或者说标题Quarterly Revenue 季度营收。这种需求从matplotlib 3.6开始有了官方解法font fallback。所谓字体回退就是渲染器在逐字符绘制时如果第一个字体缺少某个字符的字形就自动尝试列表里的下一个字体直到找到有对应字形的字体为止。用法就是给family传一个列表mixed_font FontProperties(family[Times New Roman, Noto Sans CJK SC], size14) ax.set_title(Quarterly Revenue 季度营收, fontpropertiesmixed_font) ax.legend(propFontProperties(family[Times New Roman, Noto Sans CJK SC]))渲染到Quarterly Revenue这些ASCII字符时Times New Roman里有字形就用Times渲染到季度营收时Times里没有CJK字形回退到Noto Sans CJK SC。这样在同一个Text对象内部实现了中英文各归各的字体代码只有一行改动。同样可以写在全局rcParams里plt.rcParams[font.family] [Times New Roman, Noto Sans CJK SC]不过我要提醒一句字体回退在不同后端下的支持策略不同。我实测下来Agg最常见的PNG保存后端、TkAgg等交互后端表现稳定PDF和PS矢量后端对fallback的支持在3.6到3.8这代版本里曾经有明显缺陷如果最终目标是要提交PDF版本建议先用PNG导出确认效果或者升级到较新版本再看官方release notes。别把希望全押在一个特性上。3.3 严格混排手写字符拆分绘制工具如果你的环境卡在matplotlib 3.6之前不能升级或者目标后端对fallback支持确实不理想还有一种更古老但也绝对可控的办法——把混合字符串拆成若干纯中文段和纯英文段每段单独用一个text对象绘制再利用文本宽度计算把位置拼成一行。这是我实际项目里用过的工具函数兼容性极好import re from matplotlib.font_manager import FontProperties def draw_mixed_text(fig, ax, x, y, s, cn_font, en_font, vabottom, haleft): 在数据坐标系(x, y)位置绘制字符串s中文段用cn_font英文段用en_font。 返回结束位置的横坐标。 fig.canvas.draw() renderer fig.canvas.get_renderer() tokens re.findall(r[\u4e00-\u9fff]|[^\u4e00-\u9fff], s) cur_x x for token in tokens: is_cjk bool(re.match(r[\u4e00-\u9fff], token)) fp cn_font if is_cjk else en_font t ax.text(cur_x, y, token, fontpropertiesfp, vava, haha) bb t.get_window_extent(rendererrenderer) cur_x ax.transData.inverted().transform((bb.x1, 0))[0] return cur_x使用示例import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(8, 3)) cn_font FontProperties(familySimHei, size14) en_font FontProperties(familyTimes New Roman, size14) draw_mixed_text(fig, ax, 0.1, 0.5, Dataset A 数据集A, cn_font, en_font) ax.set_xlim(0, 1) ax.set_ylim(0, 1) fig.savefig(mixed_text.png, dpi150)这个函数有四个容易翻车的地方提前给你打好预防针fig.canvas.draw()必须提前调用否则GetRenderer()拿不到渲染器调用会直接失败。get_window_extent返回的是窗口坐标系下的像素宽高宽度取决于当前坐标轴范围和figsize。如果你的代码在绘制之后再改xlim、ylim或figsize已经算好的位置不会自动更新所以调用顺序是先定布局再画混合文本最后保存。正则[\u4e00-\u9fff]覆盖基本汉字对生僻字或扩展区字符不够用需要把范围扩到\u3400-\u4dbf。如果混排文本里还有日文假名、韩文得再加对应Unicode区间。这个方法本质是多个text对象拼接。在导出的PDF里每个文字段还是独立的文本对象复制搜索时可能不是一个连续句子但视觉上没有任何差别。还有一个连带问题图例legend里的文本没法用这个函数直接重排因为图例条目是matplotlib内部自动创建的。为了严格混排图例文本要么用fallback要么就别在同一个图例条目里混排中英文分开写条目反而更清晰。3.4 标签拆分到不同位置最省事的旁路方案如果所谓中英文不同字体指的是英文主标题加中文副标题或者英文单位加中文说明那完全不需要任何混排技巧直接用位置错开的两个text对象ax.set_title(Quarterly Revenue, fontpropertiesen_font, pad20) ax.text(0.5, 1.02, 季度营收, transformax.transAxes, fontpropertiescn_font, hacenter, vabottom)用ax.transAxes可以按坐标轴比例定位主标题和副标题一上一下风格各管各的。很多论文配图和正式报表里的中英文标题分行就是这个套路。它的优点是完全绕开字体选择机制缺点是中英文不在同一行视觉上不是严格混排。具体选哪种就看你的场景给团队看的内部报表怎么快怎么来要投稿的插图才值得上字符拆分。4. 实操过程一套可以直接抄走的代码4.1 环境准备与字体体检先做环境体检避免盲猜。任何画图脚本我建议开头先打出一份字体清单和matplotlib版本import matplotlib import matplotlib.pyplot as plt from matplotlib import font_manager print(matplotlib version:, matplotlib.__version__) cjk_names sorted({ f.name for f in font_manager.fontManager.ttflist if any(kw in f.name for kw in (CJK, Hei, YaHei, Song, Kai, PingFang, WenQuanYi)) }) print(candidate CJK fonts:, cjk_names)如果输出为空别浪费时间调rcParams先去装字体。装完之后如果还是空按第5章的缓存排查方法处理。这一步做完能省掉后面90%的排查时间。4.2 完整案例全局中文 局部英文 混合图例下面这个案例模拟一个数据报表场景主题是英文轴标签和注释是中文图例里中英文混排。代码可以直接复制到你的脚本里import numpy as np import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties plt.rcParams[font.family] sans-serif plt.rcParams[font.sans-serif] [Noto Sans CJK SC, SimHei, DejaVu Sans] plt.rcParams[axes.unicode_minus] False cn_font FontProperties(familyNoto Sans CJK SC, size12) en_font FontProperties(familyTimes New Roman, size12) mixed_font FontProperties(family[Times New Roman, Noto Sans CJK SC], size16) rng np.random.default_rng(42) x np.arange(1, 13) y np.cumsum(rng.normal(2, 5, size12)) fig, ax plt.subplots(figsize(9, 4.5)) ax.plot(x, y, markero, linewidth2, labelMonthly Revenue 月度营收) ax.set_title(2024 Revenue Trend 营收趋势, fontpropertiesmixed_font) ax.set_xlabel(Month 月份, fontpropertiescn_font) ax.set_ylabel(Revenue (万元), fontpropertiescn_font) ax.legend(propFontProperties(family[Times New Roman, Noto Sans CJK SC])) ax.grid(True, linestyle--, alpha0.6) fig.savefig(revenue_trend.png, dpi200)这里要特别解释一下mixed_font的用法family参数传入的是一个列表Times New Roman在前、Noto Sans CJK SC在后。这就是触发字体回退的关键。如果你只传单个字体Times New Roman给标题中文部分的行为就不可控了——新版可能回退到默认字形兜底旧版直接豆腐。显式传入列表代码意图就非常清晰英文走Times中文走Noto。图例里的labelMonthly Revenue 月度营收同样依赖字体回退机制所以prop也必须传一个family为列表的FontProperties对象。如果你的matplotlib版本低于3.6这两个地方的混排文字会出问题解决方式就是升级版本或者把图例条目拆成纯英文和纯中文两个。4.3 导出格式的细节保存PNG时dpi直接决定字体清晰度。日常屏幕看图150dpi够用报告PPT插图建议200dpi要印刷至少300dpi。保存PDF/SVG这类矢量格式时字体有两种行为要么嵌入文件内部要么只记录字体名不嵌入由阅读器所在系统提供字体替换。如果目标机器没有对应字体SVG打开后字体会被浏览器自动替换PDF也一样。所以给别人的矢量图要么确认对方系统有同一套字体要么导出高清PNG一劳永逸。我个人的习惯是最终交付件一律出PNGPDF只在确实需要矢量精度的场景下使用而且出完PDF要在另一台机器上打开检查一遍。4.4 和LaTeX/mathtext共存时的注意点论文图里常有数学公式需求这里有个边界问题plt.rcParams[text.usetex]True启用之后matplotlib把文本渲染全部交给外部LaTeX引擎rcParams里的字体列表基本失效中文字符在默认LaTeX配置下直接缺字或编译报错。我的经验是论文图里要混排中文不要全局开启usetex。公式用mathtext的内联语法$...$中文放在$之外。这样公式部分走mathtext的数学字体中文部分走rcParams设置的字体各管各的。要是真需要xelatex那种完整中文LaTeX排版那是另一个系统工程别指望靠matplotlib的rcParams解决。5. 我踩过的坑常见问题速查清单5.1 字体缓存不刷新表现新安装的中文字体在matplotlib里死活看不到。原因font_manager缓存的fontlist文件没更新。处理删除~/.cache/matplotlib/fontlist-*.json后重启Python或者用addfont紧急加载。这个坑卡了我整整一个下午装上字体后无论如何都提示找不到最后清掉缓存重置才正常。5.2 负号变成方块表现x轴有负值时出现方块正数全正常。原因坐标轴刻度默认用Unicode减号U2212很多中文字体没有这个字形。处理加上axes.unicode_minusFalse。这个设置我无论画什么图都写进全局配置哪怕当时没中文也不差这一行。5.3 中文字体加粗变成毛刺表现设置了fontweightbold中文字体边缘明显粗糙甚至笔画重叠。原因SimHei这类字体没有bold实体matplotlib用算法对普通字形做合成粗体效果很差。处理换成自带Bold变体的字体比如Noto Sans CJK SC Bold或者用fontweightsemibold这类较轻的权重。5.4 ttc字体加载不完整TTC文件是多个字体的合集addfont对ttc的支持并不完美有时只注册第一个face有时family列表里冒出一堆奇怪名字。遇到过TTC加载之后ttflist里出现十几个带语言后缀的名字如果不小心选错了中文字形可能变成日文风格或韩文风格。建议优先使用TTF/OTF单字体文件非用TTC不可时打印ttflist逐项确认family名再使用。5.5 常见问题速查表症状几乎可以断定最快处理中文全是豆腐块当前字体没有CJK字形把中文字体加进sans-serif候选列表中文字体生效但英文也变中文字体中文字体排在第一位且没有fallback升级3.6family列表用英文中文负号变方块缺U2212字形axes.unicode_minusFalse图例字体没变参数名写错用propFontProperties(...)新装字体不识别缓存未刷新删fontlist缓存或addfont动态注册中文字体加粗发毛字体无bold实体合成粗体换自带Bold的字体如Noto CJKTTC加载后字体风格不对选错face中了日文/韩文变体打印ttflist逐个确认family名5.6 最后的检查习惯出图之后不要只看屏幕缩略图把PNG放大到100%再检查一遍中英文交界处的字形粗细和基线对齐。字体问题在缩略图里几乎看不出来一旦放进PPT或送印刷所有瑕疵全暴露。我个人习惯是在每个画图脚本顶部放一个字体体检和rcParams设置的小函数所有中文字体和英文字体都在这一个地方管理。一旦某个环境出问题先跑体检再按速查表逐项排除基本几分钟就能定位。字体设置这件事本身不复杂但组合上操作系统、matplotlib版本、导出格式坑位一下子多出好几倍。把这些固定套路沉淀成脚本模板之后matplotlib的字体问题就不再是玄学而是完全可控的工程步骤了。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

无经验怎么积累测试项目?两周搭建一份能写进简历的实战经历 2026/10/2 18:33:26

无经验怎么积累测试项目?两周搭建一份能写进简历的实战经历

大概每个准备入行软件测试的人,都经历过这种卡壳时刻:测试基础看完了、用例设计方法背熟了、面试八股文也过了一轮,打开简历模板,光标停在“项目经验”那一栏,半天敲不出一个字。我在帮人改简历的时候,几乎…

阅读更多 →
Hudi + Hive 增量数据处理全攻略:从同步机制到小文件优化 2026/10/2 18:33:26

Hudi + Hive 增量数据处理全攻略:从同步机制到小文件优化

做网约车大数据项目那段时间,每天几十亿条订单、轨迹、支付流水往数据平台涌。团队最头疼的并不是数据量大,而是“变化”本身:订单状态不停更新、司机位置持续漂移、部分记录还要回滚删除。如果还是按离线思路每天全量重跑,计算资…

阅读更多 →
端到端数字产品交付:从概念到上线的链路设计与实践 2026/10/2 18:33:26

端到端数字产品交付:从概念到上线的链路设计与实践

前段时间有个做产品的朋友问我:现在到处都在说“端到端”,是不是以后一个人把所有事干了就行?这个问题把我问笑了。“端到端”这三个字确实被说烂了,尤其是智驾圈,隔三差五一个端到端大模型。但在数字产品交付的语境里…

阅读更多 →
OpenHarmony上Flutter数独App:本地数据持久化实战 2026/10/2 18:33:26

OpenHarmony上Flutter数独App:本地数据持久化实战

1. 项目盘点:在OpenHarmony上跑Flutter,先把账算清楚 1.1 这个App到底要做什么 先把这个项目的边界说清楚:这不是一个纯ArkUI的鸿蒙原生应用,也不是一个跑在Android套壳环境里的普通Flutter应用,而是直接让Flutter引擎…

阅读更多 →
无线信道特性如何驱动系统设计?从链路预算到OFDM参数 2026/10/2 18:33:18

无线信道特性如何驱动系统设计?从链路预算到OFDM参数

有没有遇到过这样的怪事:手机屏幕上的信号格明明满着,视频却在转圈;站在窗边网速飞快,往屋里挪几步就开始卡顿;同一条走廊里测速,换个方向结果差出好几倍。以前我做无线系统设计时也困扰了很久,…

阅读更多 →
PHP搜索聚合站源码部署实战:从环境配置到SEO优化全流程指南 2026/10/2 18:33:17

PHP搜索聚合站源码部署实战:从环境配置到SEO优化全流程指南

简介:搜聚合网站源码是一套面向个人站长、新手开发者及需要快速搭建聚合导航站的用户的轻量级PHP网站程序,内置多平台热搜聚合、分类筛选、站内搜索、天气预报和访问统计等功能,无需数据库配置即可直接部署运行。它采用响应式布局与缓存机制&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉