当前位置:首页>python>Python学习|Matplotlib 字体渲染深坑:当中文宋体遇上数学公式,终极解决方案!

Python学习|Matplotlib 字体渲染深坑:当中文宋体遇上数学公式,终极解决方案!

  • 2026-10-11 06:14:29
Python学习|Matplotlib 字体渲染深坑:当中文宋体遇上数学公式,终极解决方案!

点击上方蓝字 关注我们

2026

在遥感测绘数据可视化中,Matplotlib的地位至今无可撼动。无论是论文插图、报告输出还是日常的数据探查,它都是我们离不开的工具。然而这在输出中文图面美化时,却有一个延续了十多年的“阿喀琉斯之踵”,也就是字体渲染问题。

诸君,我们常说“字体乱象,此处暂且不表”,但今天,我们不仅要表,还要把它的表的彻底。从最基础的中文配置,到纯文本与数学公式混合渲染时的“字体回退失效”,再到开发者社区长达两年的拉锯战,本文将为你抽丝剥茧,并奉上最新、最优雅的解决方案。

Part.01

基础——两行代码解决80%痛点

各位,我们绝大多数中文用户遇到的第一个门槛,就是Matplotlib的默认字体压根不包含中文字形。于是图表标题、坐标轴标签上的中文全部变成了恼人的小方框,也就是☐☐☐☐☐☐☐……

然而目前流传的解决方案大多集中在修改rcParams配置文件。这确实有效,也是正统的解决之道。假设我们希望图表中的英文使用Times New Roman,中文使用宋体(SimSun),经典代码如下:

importmatplotlib.pyplot as pltplt.rcParams['font.family'] = ['Times New Roman', 'SimSun']plt.rcParams['axes.unicode_minus'] = Falseplt.text(0.5, 0.5, "长风破浪会有时,直挂云帆济沧海。\nData Science & Visualization",          ha='center', va='center', size='xx-large')plt.show()

且看此处效果:

此时,纯文本场景下的中英文混排堪称完美。看起来问题已经解决了,不是吗?

诸君,尚且留步,真正的问题才刚刚开始。

Part.02

问题——公式环境下“字体回退”

当你试图在图表中加入数学公式时,你会发现刚刚那套方法是全然走不通了。那个令人恼火的缺字黑方框又回来了,而且是在中文字符的位置!

你可能会去国内的搜索引擎翻找答案,但很快就会发现一个残酷的问题,看似到处都是文章,实则全是翻来覆去转载的屎山。所有的教程都在rcParams里的那几个参数(font.sans-serif、font.family等)兜圈子,对公式环境中的乱码毫无办法。

这是为什么?因为Matplotlib底层的字体渲染机制远比我们想象的要复杂。

通过翻阅Matplotlib的源码(matplotlib.textpath.TextToPath类),我们会发现,文本转矢量路径的过程被分为了三种截然不同的情况:

class TextToPath:    def get_text_path(self, prop, s, ismath=False):        if ismath == "TeX":          # 分支1:LaTeX 全量渲染            ...         elif not ismath:             # 分支2:纯文本(字体回退)            font = self._get_font(prop)            ...        else:                        # 分支3:数学公式(专用引擎)            glyph_info = self.get_glyphs_mathtext(prop, s)            ...

也就是:

  1. LaTeX 模式:全权交给系统 LaTeX 处理,不在本次讨论范畴。

  2. 纯文本模式:Matplotlib 3.6 版本之后,官方支持了Fallback,也就是字体回退功能,即当 Times New Roman 缺少中文字形时,会向下查找列表中的 SimSun 进行补充。

  3. 数学公式模式(Mathtext):这是万恶之源。当文本中包含 $...$ 公式符号时,渲染会走内置的 mathtext 。该引擎自诞生之初,就没有实现字体回退机制!

官方开发文档早在 2024 年就在 Issue #29173 中坦承:I don't believe mathtext ever had font fallback implemented since the initial implementation.(我不认为 mathtext 自从初始实现以来有过字体回退功能)。所以这意味着,公式框内的纯中文部分,永远只会拿着 Times New Roman 去解析汉字,结果自然是查无此字,只能由黑框代替。

Part.03

破局——jchliao的visioplot与猴子补丁

诸君,难道我们要为了一个公式,放弃优雅的 Times New Roman,把全局字体改成中文字体,从而让英文字母变得不堪吗?当然不了。

直到 2026年4月24日,一位名为 jchliao 的开发者提交了一个堪称神之一手的临时解决方案——visioplot 库。这个库的巧妙之处在于,它没有要求用户修改任何原有的绘图逻辑,仅仅通过import这一动作,就悄无声息地修复了底层 BUG。

其核心原理是 Monkey Patch。在 visioplot 库的 __init__.py 初始化文件中,它动态地将 matplotlib 内部处理公式字形的方法 UnicodeFonts._get_glyph 替换为了带有字体回退逻辑的装饰器函数:

from matplotlib._mathtext import UnicodeFonts# 替换掉不支持回退的内部函数UnicodeFonts._get_glyph = mathtext_fallback_decorator(UnicodeFonts._get_glyph)

由于 Python 的导入机制会在程序启动时执行包内的 __init__.py,因此即使你不显式调用 visioplot 的任何函数,只要它被导入,补丁就会生效。

我们换一组包含经典豪迈诗词和数据科学公式的文本,见证这个方法:

import matplotlib.pyplot as pltimport visioplotplt.rcParams['font.family'] = ['Times New Roman', 'SimSun']plt.rcParams['axes.unicode_minus'] = Falsefig, ax = plt.subplots(figsize=(12, 6))text = (r"《行路难》李白:长风破浪会有时,直挂云帆济沧海。"        "\n\n"        r"Regression Analysis: $\hat{y} = \beta_0 + \beta_1 x_1 + \epsilon$"        "\n\n"        r"Evaluation Metric: $R^2 = 1 - \frac{SS_{res}}{SS_{tot}}$")ax.text(0.5, 0.5, text, ha='center', va='center', size='x-large',         transform=ax.transAxes)ax.set_title(r"中文宋体与英文 Times New Roman 及公式 $\sum x_i$ 完美共存",              fontsize=16)plt.tight_layout()plt.show()

我们便可以看到如下的效果:

或者我们可以换一种方式,将公式也用Times New Roman字体显示:

import matplotlib.pyplot as pltimport visioplotplt.rcParams['font.family'] = ['Times New Roman', 'SimSun']plt.rcParams['axes.unicode_minus'] = Falseplt.rcParams['mathtext.rm'] = 'Times New Roman'plt.rcParams['mathtext.it'] = 'Times New Roman:italic'plt.rcParams['mathtext.bf'] = 'Times New Roman:bold'fig, ax = plt.subplots(figsize=(12, 6))text = (r"《行路难》李白:长风破浪会有时,直挂云帆济沧海。"        "\n\n"        r"Regression Analysis: $\hat{y} = \beta_0 + \beta_1 x_1 + \epsilon$"        "\n\n"        r"Evaluation Metric: $R^2 = 1 - \frac{SS_{res}}{SS_{tot}}$")ax.text(0.5, 0.5, text, ha='center', va='center', size='x-large',         transform=ax.transAxes)ax.set_title(r"中文宋体与英文 Times New Roman 及公式 $\sum x_i$ 完美共存",              fontsize=16)plt.tight_layout()plt.show()

效果如下:

Part.04

安装与生态展望

这个救星库的安装非常简单,目前虽然未上线 conda 默认通道,但主流渠道均已支持:

# 方式一:pip 安装pip install visioplot# 方式二:conda-forge conda install -c conda-forge visioplot# 方式三:mamba 安装mamba install visioplot

既然问题已经能被补丁修复,Matplotlib 官方团队自然也没有闲着。2026年5月官方 PR(#31766)已被提交,该 PR 实现方法有所不同(直接从 Mathtext 引擎底层传入字体列表),但目前仍在审查阶段,尚未合并入主分支。

不过,鉴于核心开发者已经关注到这一问题,相信在未来的小版本更新中这一特性将被原生支持。届时,我们将不再需要额外引入补丁库。

回顾整个问题的演进,一个小小的字体回退,折射出的是国际开源项目在处理多语言复杂排版时的天然短板。而正是因为有了 jchliao 这样愿意钻研底层源码、并通过巧妙补丁回馈社区的开源开发者,我们才能在官方修复之前,提前享受无障碍的科研绘图体验。

感谢开源,感谢奉献! 

如果你觉得“原来Python也能讲得这么遥感”,点个关注,后续系列推文将第一时间推送:

  • 文件与目录操作

  • NumPy入门

你在处理遥感数据时,遇到过哪些让循环“卡住”的场景?是千万级像元的遍历太慢,还是海量文件的批处理总报错?评论区聊聊,下期可能就针对你的痛点来讲解

用Python讲遥感,我们不讲噱头,只讲干货。

最新文章

随机文章