Matplotlib中文显示方块?一文讲透字体配置与乱码解决

Matplotlib中文显示方块?一文讲透字体配置与乱码解决 如果你用 Python 画过带中文的图一定见过那一排排方方正正的“豆腐块”。Matplotlib 默认字体并不认识中文所以坐标轴、图例、标题里凡是出现汉字的地方渲染出来全变成一个个空心方块。我第一次踩这个坑是在做月度销售报表数据算得一点没错图表标题却惨不忍睹当时还以为是 CSV 编码出了问题后来才发现问题根本不在数据而是绘图库的中文字体配置没处理。Matplotlib 中文显示问题本质上是“字体缺失”加“默认配置不匹配”。这篇博文会把背后的原理、三种主流设置方案、三大平台的实操细节以及负号变方块、缓存不刷新这类高频故障一次讲清楚。适合刚上手 Python 数据可视化的入门者也适合经常在服务器上跑脚本、换一台机器就遇到乱码的开发者。1. Matplotlib 中文显示问题的根源默认字体不认识汉字1.1 为什么默认字体“不背锅”Matplotlib 默认使用的字体是 DejaVu Sans这套字体的字符集主要集中在拉丁文、希腊文、西里尔文等并没有内置中文字形。中文字符数量庞大一套完整的中文字体动辄几 MB 甚至十几 MBMatplotlib 为了保持安装包体积和跨平台一致性不可能默认带一份中文字体所以它在渲染汉字时找不到对应字形就只能退化成空心方块。这里有个容易误解的地方操作系统里明明装了微软雅黑、苹方这些中文字体但 Matplotlib 依然显示方块。原因是 Matplotlib 启动时会扫描并维护一份自己的字体注册列表默认列表里可能根本没有系统字体项。它“知道”的字体和系统里“存在”的字体是两码事。所以解决问题的核心是让 Matplotlib 的字体列表里出现一个能显示中文的字体然后把它设置为首选字体。在动手之前先记住这个判断标准如果图里出现的是方块而且这些方块的位置正好对应汉字字符那基本就是字体问题。如果出现的是问号、乱码符号那可能是数据编码问题。这两类问题不要混在一起排查。1.2 先看看你的环境里有哪些中文字体可以用在配置之前先用代码查一下 Matplotlib 当前到底认哪些字体。打开 Python 环境运行下面这段代码from matplotlib import font_manager fonts sorted(set(f.name for f in font_manager.ttflist)) for f in fonts: print(f)这段代码会列出 Matplotlib 已经注册的全部字体名称。如果列表很长你可以用关键词筛出中文字体比如包含 Hei、Song、Kai、PingFang、Noto、WenQuanYi 等字样的字体from matplotlib import font_manager keywords [Hei, Song, Kai, PingFang, Noto, WenQuanYi, YaHei, SimSun] for f in font_manager.ttflist: if any(k in f.name for k in keywords): print(f.name, -, f.fname)注意一点ttflist只包含本次 Python 进程启动时扫描到的字体。如果你刚刚在系统里安装了一个新字体但没有重启 Python这个列表里是不会出现的。这是个特别常见的坑后面会专门讲。1.3 Windows、macOS、Linux 各自有哪些常用中文字体不同平台的可用字体不一样选对字体名是成功的一半。我把几个平台上最常用的中文字体整理成了对照表方便你快速定位。平台常用中文字体说明WindowsSimHei黑体、Microsoft YaHei微软雅黑、SimSun宋体、KaiTi楷体、FangSong仿宋SimHei 是最经典的选择笔画清晰图表场景下最不容易出错macOSPingFang SC苹方、STHeiti华文黑体、Songti SC宋体-简、Hiragino Sans GB苹方是 macOS 默认中文字体显示细腻适合高分辨率屏幕LinuxNoto Sans CJK SC、WenQuanYi Micro Hei文泉驿微米黑、Source Han Sans SC思源黑体大多需要手动安装字体包安装后效果不输商业字体这里特别提醒一下设置字体时用的是字体的“名称”不是字体文件名。比如 Windows 的字体文件是msyh.ttc但字体名称是Microsoft YaHei。搞混这个配置写了也白写。2. 三种设置方式按场景选2.1 全局 rcParams 配置日常项目里最常用绝大多数场景下你只需要在代码开头设置两行配置就能让整张图里的中文正常显示import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Microsoft YaHei, SimHei, Noto Sans CJK SC] plt.rcParams[axes.unicode_minus] False第一行设置中文字体第二行解决负号问题负号问题后面专门说。这里的关键点在于font.sans-serif的赋值是一个列表Matplotlib 会从前往后尝试第一个字体找不到就找第二个。所以写一个候选列表比只写一个字体更稳健换一台机器也不容易翻车。第二行axes.unicode_minus False必须跟着一起配否则你会发现中文正常了但图里的负号变成了一个个小方块。原因是很多中文字体没有专门的数学负号字形禁用 Unicode 负号后Matplotlib 会用普通的连字符来画负号。这两行配置要放在所有绘图代码执行之前而且要保证同一个进程里只设置一遍。如果在 Jupyter 里运行切记先重启 Kernel 再执行配置不然后面的绘图可能还是旧的默认字体。2.2 临时上下文方式只在特定图表范围内生效如果不想影响全局配置只想让某一张图用中文字体可以用rc_context做临时设置代码执行完自动恢复原状from matplotlib import rc_context import matplotlib.pyplot as plt with rc_context({font.sans-serif: [WenQuanYi Micro Hei], axes.unicode_minus: False}): plt.figure(figsize(6, 4)) plt.plot([1, 2, 3], [1, 4, 2]) plt.title(临时中文字体测试) plt.xlabel(横轴) plt.ylabel(纵轴) plt.show()这种方式的好处是隔离作用域。比如你在做一个自动化报告生成脚本大多数图默认用英文排版只有个别图需要输出中文用rc_context包住那几张图就行不会污染全局。我之前调试不同字体风格时也习惯用这种方式切换字体非常方便。2.3 matplotlibrc 配置文件一次配置整个项目生效如果你希望整个项目、甚至整个工作目录下的所有脚本都自动使用中文字体可以创建一份matplotlibrc配置文件。在项目根目录新建一个名为matplotlibrc的文件写入以下内容font.sans-serif : Microsoft YaHei, SimHei, Noto Sans CJK SC axes.unicode_minus : False只要 Python 进程的工作目录是这个项目目录Matplotlib 启动时就会自动读取这份配置不需要在代码里写任何rcParams。这在团队协作里非常有价值大家 clone 代码后配好字体出图效果完全一致。需要验证当前生效的配置来自哪个文件时可以运行import matplotlib print(matplotlib.matplotlib_fname())它会打印出当前正在使用的配置文件路径。如果显示的是系统自带的matplotlibrc说明项目目录下的配置没被加载多半是工作目录不对。3. 三大平台实操一个步骤都不漏3.1 Windows 下的完整操作流程Windows 是最容易配好的平台因为系统里基本都会自带中文字体。操作步骤很简单第一步确认系统字体存在。打开C:\Windows\Fonts查看有没有msyh.ttc或simhei.ttf。正常情况下都有。第二步运行字体扫描脚本确认 Matplotlib 能识别到字体。执行前面写的font_manager.ttflist筛选代码如果输出的字体列表里能看到Microsoft YaHei或SimHei直接进入下一步。如果看不到把 Python 进程完全退出再重开一次。第三步在代码里写入配置import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False plt.figure(figsize(6, 4)) plt.plot([1, 2, 3], [1, 4, 2]) plt.title(中文标题示例) plt.xlabel(X 轴) plt.ylabel(Y 轴) plt.show()有人可能在上面代码里发现 SimHei 和 Microsoft YaHei 的顺序问题。我一般推荐把 SimHei 放前面因为它在纯图表的显示场景下字形更紧凑不容易出现文字挤压。但如果你更喜欢微软雅黑的现代感把 Microsoft YaHei 放前面也完全没问题这是个人审美取舍。3.2 macOS 下的字体选择与配置细节macOS 的中文字体比较丰富但有个小坑PingFang SC 在 Matplotlib 的字体列表里可能显示为带前缀的样式名具体写法因版本而异。最稳妥的做法是打开终端执行fc-list :langzh查看系统里可用的中文字体然后到 Python 里用扫描脚本确认 Matplotlib 使用的字体名称。如果系统没有fc-list命令可以先安装 fontconfigbrew install fontconfig装上之后执行fc-list :langzh你会在输出里看到类似PingFang SC、STHeiti这样的名称。然后在 Matplotlib 里配置import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [PingFang SC, Heiti SC, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False如果PingFang SC没有被正确识别用扫描脚本看一下实际注册名称改成那个名称即可。需要提醒的是macOS 上的 Arial Unicode MS 也支持中文而且兼容性不错但字形偏老显示效果不如苹方。3.3 Linux 服务器先装字体再谈配置Linux 服务器上的问题往往不是配置不对而是系统里压根没有中文字体。尤其是一些精简版镜像连 fontconfig 都没装全。这种情况下你先别急着改 rcParams先把字体装上。Ubuntu / Debian 系执行sudo apt update sudo apt install -y fonts-noto-cjk fc-cache -fvCentOS / RHEL 系执行sudo yum install -y google-noto-sans-cjk-fonts fc-cache -fv字体装好之后重启你的 Python 进程再运行字体扫描脚本确认Noto Sans CJK SC已经出现在列表中。然后在代码里配置plt.rcParams[font.sans-serif] [Noto Sans CJK SC, WenQuanYi Micro Hei] plt.rcParams[axes.unicode_minus] False如果你的服务器没有 root 权限也可以把字体文件放到用户目录mkdir -p ~/.fonts cp your_font.ttf ~/.fonts/ fc-cache -fv再重启 Python 就能识别。这个方法在远程开发环境、Docker 容器里同样适用。4. 进阶排查与实践经验4.1 负号突然变方块axes.unicode_minus 是必配项很多人在配好中文字体后发现坐标轴负号变成了方块。原因我在前面提过中文字体不一定包含 U2212 这个 Unicode 负号字形而 Matplotlib 默认就是用它来渲染负号的。设置axes.unicode_minus False后Matplotlib 会改用 ASCII 的连字符-方块问题自然消失。这个配置我强烈建议养成都写的习惯不管当前图里有没有负号。因为图表内容是不可预知的今天没有负号不代表明天没有提前写上能省不少事。实际项目里我还遇到过一种情况图里没有负号但用了plt.xticks手动设置了刻度标签标签文本里带了短横线结果也被渲染成方块。这个同样是unicode_minus的锅设置False就能解决。4.2 改了配置还是不生效先清字体缓存再说遇到“明明设置了中文字体重新运行还是方块”的情况多数时候是字体缓存没刷新。Matplotlib 第一次启动时会扫描系统字体并把结果缓存到用户目录下的fontlist-*.json文件里不同版本文件名略有差异。新安装的字体如果没有被重新扫描就不会出现在ttflist里配置写了也找不到。解决方案是删除缓存文件后重启进程。缓存路径一般在WindowsC:\Users\你的用户名\.matplotlib\下的fontlist-*.jsonmacOS / Linux~/.matplotlib/下的fontlist-*.json你可以直接删掉整个.matplotlib目录下的缓存文件不影响其他配置最多让 Matplotlib 下次启动重新扫描一次字体。删除缓存后记得重启 Python 进程或者重启 Jupyter Kernel。这一步很多人漏掉导致改了缓存之后依然乱码。如果你在用 Jupyter重启 Kernel 是必须动作光是重新运行单元格不够。4.3 使用自定义字体文件彻底摆脱系统依赖还有一种更稳的方案直接加载项目目录里的字体文件完全不依赖操作系统安装了什么字体。这个方法我强烈推荐用在自动化报告、团队共享脚本里。首先准备一个字体文件比如思源黑体或阿里普惠体的 TTF 文件放进项目assets/fonts/目录。然后有两种用法。第一种局部指定from matplotlib.font_manager import FontProperties import matplotlib.pyplot as plt font_path assets/fonts/AlibabaPuHuiTi-3-55-Regular.ttf font_prop FontProperties(fnamefont_path) plt.plot([1, 2, 3], [1, 4, 2]) plt.title(自定义字体标题, fontpropertiesfont_prop) plt.xlabel(X 轴, fontpropertiesfont_prop) plt.ylabel(Y 轴, fontpropertiesfont_prop) plt.show()第二种全局注册from matplotlib import font_manager font_path assets/fonts/AlibabaPuHuiTi-3-55-Regular.ttf font_manager.fontManager.addfont(font_path) prop font_manager.FontProperties(fnamefont_path) plt.rcParams[font.sans-serif] [prop.get_name()] plt.rcParams[axes.unicode_minus] Falseaddfont方法会把字体文件注册到当前进程的字体列表中然后通过prop.get_name()获取字体名称再设置到font.sans-serif。这样整个脚本里所有图表都能用这个字体而且不依赖系统环境。团队协作时只要把字体文件一起放进仓库任何同事拉下代码都能复现同样的效果。4.4 跳出 Matplotlib中文方块问题在其他场景里同样存在中文显示成方块并不只是 Matplotlib 的专利。嵌入式领域常见的 TFT LCD 屏幕显示中文本质上是主控端没有加载对应的中文字库或字模需要烧录字库并保证编码一致桌面软件比如 VSCode 打开文件出现乱码往往是文件编码与编辑器默认编码不匹配Zabbix 图表标题显示方框通常也是服务器上缺少中文字体。这些问题背后的共性都是渲染引擎本身没问题缺的是“合适的字体数据”和“正确的配置”。所以我平时接到“XX工具中文乱码”的咨询时第一句话永远是一样的先把系统里能显示中文的字体装上再确认编码方式最后检查缓存或配置。这个排查顺序在 Matplotlib 里适用放到 LCD 屏、监控大屏、报表工具里同样适用。学会一套思路能解决一大片问题。按照我自己的习惯现在写 Matplotlib 脚本已经有了一套固定动作先跑一遍字体扫描脚本确认当前平台有哪些中文字体设置font.sans-serif时永远写一个候选列表而不是单个字体axes.unicode_minus False随手写上如果换了新环境第一时间检查字体缓存。这套流程让我少踩了无数次重复的坑。你从这篇内容里拿走这套流程大概率也能一次配好中文显示不用再浪费时间跟方块较劲。