各位,我们绝大多数中文用户遇到的第一个门槛,就是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()
且看此处效果:
当你试图在图表中加入数学公式时,你会发现刚刚那套方法是全然走不通了。那个令人恼火的缺字黑方框又回来了,而且是在中文字符的位置!
你可能会去国内的搜索引擎翻找答案,但很快就会发现一个残酷的问题,看似到处都是文章,实则全是翻来覆去转载的屎山。所有的教程都在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) ...
也就是:
诸君,难道我们要为了一个公式,放弃优雅的 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()
效果如下:
这个救星库的安装非常简单,目前虽然未上线 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 这样愿意钻研底层源码、并通过巧妙补丁回馈社区的开源开发者,我们才能在官方修复之前,提前享受无障碍的科研绘图体验。
感谢开源,感谢奉献!