
1. 先认症状Linux下matplotlib画图中文不显示的几种形态先把话说在前头这个问题几乎每个在Linux下用过matplotlib画图的人都遇到过而且它有三个长得完全不一样的“马甲”。第一种是中文变成一个个方框或小方块专业叫法是“豆腐块”第二种是整个标签位置直接空了左上角一片空白第三种是乱码比如“浠垮北”这种看着像编码错乱、实际是字体不对的符号。很多人只认第一种遇到后两种就一头雾水甚至怀疑是数据写错了其实根源都一样matplotlib没能用上支持中文字符的字体。要判断是不是同一个问题最直接的办法不是在图上瞎猜而是打开Python命令行跑两行import matplotlib之后看一下matplotlib.rcParams[font.sans-serif]默认返回的通常是[DejaVu Sans]。再跑一个print(matplotlib.rcParams[font.family])如果显示的是[sans-serif]基本就可以确认这次的中文不显示就是字体配置层面出了问题而不是数据、编码或终端显示的问题。顺带说一句Windows用户很少遇到这个坑因为微软雅黑、宋体这些中文字体出厂就装在系统里matplotlib自动能找到。Linux发行版为了体积和授权考虑很多默认不带中文字体或者只带了粗制滥造的限制版本所以同一条plt.title(中文标题)换到Linux机器上就翻车了。搞清楚这一点你就知道自己不是在跟“玄学”较劲而是在补一个“系统没替你做完的前提条件”。这篇文章就是围绕“怎么把这个前提条件补上、补完之后怎么让它长期稳定生效”来展开的既有原理也有可直接抄的命令。这篇文章适合谁读两类人最需要一类是刚把开发环境从Windows迁到Linux、或者刚在服务器上装完裸系统的实践者另一类是已经画出来了但中文还是偶尔失效、想知其然也知其所以然的进阶用户。看完之后不光要知道怎么修还要知道在哪一步修、为什么这一步能修好。2. 为什么Linux下matplotlib默认画不出中文2.1 字体不是“写了就能用”要过三道关很多人觉得matplotlib是“渲染引擎”给什么字符就画什么字符这其实是个误解。matplotlib本质上是一个“查表”的绘图库它拿到一个字符后要先在当前选中的字体文件里查询这个字符对应的字形查到了才画查不到就只能画一个空框或者直接省略。这一步类似你在一本没有某个单词的词典里查释义——词典没收录就是没收录查再多遍也没用。matplotlib默认携带的字体叫DejaVu Sans它是一套非常优秀的西文字体覆盖拉丁字母、希腊字母、西里尔字母都很完善唯独Common CJK这个区块——也就是中日韩统一表意文字——完全没有收录。所以在默认配置下让matplotlib渲染“温度变化曲线”和让一个不懂中文的人看这几个字结果是一样的只知道“有字”但不知道该画成什么样。要让中文正常显示必须同时通过三道关字体文件关Linux系统里必须真的有可用的中文字体文件比如文泉驿、Noto Sans CJK、Droid Sans Fallback等系统索引关这些字体要被fontconfig扫描并登记fc-list能查得到matplotlib配置关matplotlib的font.sans-serif搜索列表里必须包含该字体的名称或路径。很多教程只讲了第三关结果用户配置写了一大堆还是没用就是因为前两关压根没通过。下面逐个拆解。2.2 Linux字体体系和matplotlib查找机制的“双向奔赴”Linux系统里的字体管理靠的是fontconfig它维护一个字体缓存索引所有应用都通过它来查询“系统上有哪些字体”。查看中文字体的命令是fc-list :langzh这个命令会列出所有支持中文的字体文件路径和字体名称。如果这条命令输出为空说明系统里压根没有中文字体那matplotlib再怎么配置都是巧妇难为无米之炊。matplotlib查找字体的逻辑是启动时扫描系统字体目录建立自己的字体缓存然后根据用户设置的font.sans-serif列表逐个在缓存里查找对应字体找到第一个可用的就用来渲染。这个“查找”基于字体的内部名称而不是字体文件名。比如文泉驿微米黑的文件名是wqy-microhei.ttc但它的内部名称是WenQuanYi Micro Heimatplotlib配置列表里必须写后者写文件名反而找不到。这就引出一个关键矛盾matplotlib的字体缓存是在启动时建立的如果在matplotlib已经运行的情况下往系统里新装了字体它不会自动刷新识别必须重启Python进程或者手动清理缓存。很多人装了字体、改了配置、重跑了一次发现还是没变化就是因为漏了这一步。提示在动手之前先确认一下自己的matplotlib版本不同版本的字体API细节略有差异但整体思路一致。可以用python -c import matplotlib; print(matplotlib.__version__)查看。3. 第一步永远是检查系统而不是改配置3.1 用fc-list确认到底有没有中文字体我见过太多人一上来就抄plt.rcParams[font.sans-serif] [SimHei]这样的配置然后跑来问为什么还是不行。原因很简单SimHei是Windows的字体Linux系统上压根没有这个字体文件matplotlib翻遍整个字体缓存都找不到它只能默默退回默认字体相当于你给一个从不看篮球的人推荐某位退役球星的球衣号码他当然不认识。正确的第一步永远是先查系统。打开终端执行fc-list :langzh重点看两样东西输出是否为空以及输出的字体名称是什么。每一种会在输出里显示出类似WenQuanYi Micro Hei:styleRegular的格式这个冒号前面的名字就是你要写进matplotlib配置里的字体名必须一字不差大小写也要完全一致。举个实际例子在一台刚装完系统的开发机上fc-list :langzh的输出为空这时候任何配置都是无效的因为底层就没有字体可用。当你执行sudo apt install fonts-wqy-microhei这样的命令完成字体安装后再查输出里就会出现WenQuanYi Micro Hei的条目配置才有意义。如果你不想装文泉驿或者需要跟团队统一字体常见的开源中文字体还有Noto Sans CJK SC思源黑体简体、Droid Sans Fallback等。Debian系和RedHat系的包名不同安装前可以先用apt search或yum search看一下仓库里有哪些可选包挑一个覆盖量大的装就行。3.2 安装字体之后别忘了刷新字体缓存在Debian系的系统上装字体包通常会自动触发fontconfig的缓存更新但如果你是通过手动拷贝字体文件的方式安装的——比如把Windows字体目录里的某个ttf拷到~/.fonts或/usr/local/share/fonts下——系统不会自动感知新字体必须手动重建缓存fc-cache -fv这个命令会遍历所有字体目录、解析字体文件内容、生成索引缓存最后输出加入的字体数量和扫描的目录数量。执行完之后再跑一次fc-list :langzh确认新字体已经进入系统索引。这一关通过之后才轮到matplotlib侧的主题。这里还有一个容易被忽略的细节服务器场景下你很可能没有root权限。这种情况不要硬刚系统目录把字体文件放在用户目录下的~/.fonts或者~/.local/share/fonts即可fontconfig默认就会扫描这些路径。同样是执行fc-cache -fv只是权限要求完全不同普通用户身份就能完成。实测下来用户级字体目录的优先级往往比系统目录更高对后面matplotlib的字体选择反而更友好。3.3 最小验证环境先让终端“说出”中文在继续往下折腾matplotlib之前我强烈建议先做一个与matplotlib无关的验证用Python直接检查字体文件能否被读取。开一个交互式终端加载matplotlib的字体管理模块看它能不能认出新字体from matplotlib import font_manager # 列出matplotlib已缓存字体中所有包含中文相关名称的条目 for f in font_manager.fontManager.ttflist: if WenQuanYi in f.name or Noto Sans CJK in f.name: print(f.name, f.fname)这一步是踩坑后的深刻教训系统里fc-list能查到、但matplotlib列表里没有说明matplotlib的缓存是旧的此时需要清理matplotlib自己的字体缓存而不是重新安装系统字体。分开验证的好处是你能准确判断问题到底出在“系统层”还是“matplotlib层”不至于在一团迷雾里瞎调参数。4. matplotlib字体配置的四种方案与各自适用场景4.1 方案一全局rcParams配置简单粗暴最常用如果只是在自己跑脚本、画个人数据分析图最直接的方式是全局设置matplotlib的字体参数。在导入matplotlib之后、正式画图之前加入这么几行import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [WenQuanYi Micro Hei, Noto Sans CJK SC, DejaVu Sans] plt.rcParams[axes.unicode_minus] False第一行的含义是告诉matplotlib优先使用文泉驿微米黑如果没有就用思源黑体简体再不行就退回DejaVu Sans。这个列表的容量可以写多个候选字体matplotlib会按顺序逐个查找找到第一个存在的就停止。第二行的含义是关闭unicode负号。这是一个跟中文没什么关系、但几乎必然伴随中文一起出现的坑。默认情况下matplotlib会在坐标轴上用真正的Unicode字符−U2212来渲染负号但大多数中文字体并不包含这个字形结果就是负号显示成方块或直接消失。把unicode_minus设为False后matplotlib会改用ASCII连字符-代替虽然视觉上不如专用负号精致但至少不会显示异常。这个方案适合所有“我不想维护多套配置只想让本地脚本正常出图”的场景。缺点是它是进程级的每个脚本都要写一遍团队协作时容易漏写所以后面还有更规范的方案。4.2 方案二FontProperties定向指定一次性解决特定标签全局配置适合统一风格但有些场景你只是某个标签想用特定字体其他部分保持默认。比如图注用思源宋体、坐标轴用无衬线黑体这时候用FontProperties可以精确控制import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties zh_font FontProperties(fname/usr/share/fonts/truetype/wqy/wqy-microhei.ttc) plt.text(0.5, 0.5, 实验组A-温度曲线, fontpropertieszh_font, fontsize12) plt.title(中文标题, fontpropertieszh_font)这里的fname参数直接指定字体文件路径绕过了matplotlib的字体缓存查找机制相当于拿着字体文件强行塞给渲染器。好处是非常确定不管matplotlib缓存里有没有这个字体只要文件路径正确、可读就能生效。这个参数对中文不显示问题几乎是“特效药”强烈推荐在排查阶段使用——它能把变量缩减到最少快速确认问题到底是不是字体引起的。不过这个方案只对当前使用该FontProperties的文本生效如果图里有十个标签都要用中文字体就得给每个标签都传一次fontproperties参数代码会很啰嗦。另外字体文件路径要写完整写错路径会直接抛异常不像全局配置那样只是默默退回默认字体。路径可以通过fc-list :langzh的输出拿到复制粘贴即可尽量不要手打。4.3 方案三addfont动态注册面向维护的进阶选择如果你用的是较新版本的matplotlib还有一个兼顾全局和灵活性的方案先用font_manager注册字体文件再走正常的rcParams流程from matplotlib import font_manager # 注册字体文件 font_manager.fontManager.addfont(/usr/share/fonts/truetype/wqy/wqy-microhei.ttc) # 注册后该字体的内部名称就会被matplotlib缓存识别 plt.rcParams[font.sans-serif] [WenQuanYi Micro Hei] plt.rcParams[axes.unicode_minus] False这个方案的妙处在于你不需要依赖系统字体缓存是否认识这个字体直接以文件形式把字体交给matplotlib然后就能像普通字体一样用名称来引用。对于在多台服务器之间同步代码、但每台机器的字体环境不一样的团队场景把字体文件放进项目目录、启动时统一注册能最大程度保证“代码跑到哪儿中文就显示到哪儿”。缺点是需要确认matplotlib版本支持fontManager.addfont这个API旧版本没有此方法这时可以退回到方案二。同时动态注册的字体只对当前Python进程有效下一次重启要重新注册所以更推荐把注册代码封装成一个独立的函数或模块在绘图脚本开头统一调用。4.4 方案四修改matplotlibrc配置文件一劳永逸如果不想在每个脚本里重复写rcParamsmatplotlib提供了配置文件方案在项目根目录或用户目录放一个matplotlibrc文件。matplotlib启动时会自动加载这个文件里面的配置对所有脚本生效。具体做法是先找到matplotlib当前使用的配置路径python -c import matplotlib; print(matplotlib.matplotlib_fname())得到路径后在对应的matplotlibrc文件里找到这两行并修改font.sans-serif: WenQuanYi Micro Hei, Noto Sans CJK SC, DejaVu Sans axes.unicode_minus: False这里有个容易翻车的细节matplotlib_fname()找到的可能是安装目录下的系统级配置文件直接改它会影响所有用户一般不建议。更好的做法是在~/.config/matplotlib/matplotlibrc新建一个用户级配置文件matplotlib会优先读取它。这样既不影响系统文件也不影响别人只对自己生效。方案四适合需要跟团队共享配置的场景——只要把写好的matplotlibrc提交到项目仓库里其他人克隆项目后启动Python时matplotlib会自动读取项目根目录下的配置。注意matplotlib的配置文件解析规则比较严格等号两边有没有空格、注释标记都要小心最好复制官方模板再改参数不要手写。5. 实操从零到一让一个柱状图的中文标签正常显示5.1 完整的最小复现脚本理论说再多都不如跑一遍。我建议你先建一个临时目录把整个流程完整过一遍再回去改自己的绘图脚本。下面是核心演示代码保存为test_zh.pyimport matplotlib.pyplot as plt from matplotlib import font_manager # 第一步检查matplotlib是否认得了中文字体 all_fonts {f.name for f in font_manager.fontManager.ttflist} print(WenQuanYi in cache:, WenQuanYi Micro Hei in all_fonts) print(Noto Sans CJK SC in cache:, Noto Sans CJK SC in all_fonts) # 第二步手动注册一次字体文件防止缓存未更新 font_manager.fontManager.addfont(/usr/share/fonts/truetype/wqy/wqy-microhei.ttc) # 第三步全局配置 plt.rcParams[font.sans-serif] [WenQuanYi Micro Hei, Noto Sans CJK SC] plt.rcParams[axes.unicode_minus] False # 第四步画一个带负数和中文标签的柱状图 categories [一月, 二月, 三月] values [-5, 7, 3] plt.bar(categories, values) plt.xlabel(月份) plt.ylabel(温度差值 (°C)) plt.title(各月温度变化情况) plt.show()注意这里addfont的路径是按Debian系文泉驿默认安装路径写的你的机器上路径可能不同务必将路径替换成fc-list :langzh查询到的真实路径否则会抛出字体找不到的异常。跑起来之后你应该看到三个中文标签全部正常显示负号-5也正常渲染。5.2 运行时报错与缓存清理的正确姿势第一次运行时最可能出现的不是中文不显示而是addfont报FileNotFoundError原因就是路径写错。这时先用fc-list :langzh | grep wqy或fc-list | grep -i wqy确认文件路径把输出完整复制到代码里而不是凭记忆手打。另一个高频场景是你在另一个终端里运行了相同的代码发现中文还是不显示。这大概率是matplotlib的字体缓存问题。matplotlib会把扫描到的字体信息缓存在本地文件里默认路径可以通过import matplotlib; print(matplotlib.get_cachedir())查到通常是一个类似~/.cache/matplotlib的目录。当系统中字体发生变化后matplotlib不会自动发现最简单粗暴的方法是清除这个缓存目录里的所有文件再重跑rm -rf ~/.cache/matplotlib/*或者在代码里调用缓存清理接口较新版本支持from matplotlib import cache cache.delete_cache()清完之后重启Python进程重新加载matplotlib它会重新扫描系统字体刚才addfont注册的字体文件也能正确入库。我把这一步叫“字体生效三步曲”装字体、刷缓存、重启进程缺一步都可能让人白折腾半小时。5.3 我实际遇到的一个“改了配置还是不生效”的经典案例某次在给别人排查服务器上的绘图脚本时对方说“我已经按教程设置了plt.rcParams[font.sans-serif] [SimHei]但还是不显示中文”。我看了一眼就发现问题了他那台机器上根本没有SimHei这个字体系统连中文字体都没装过。这种情况下唯一正确的做法是先装字体而不是继续魔改配置。还有一次更隐蔽对方机器上装了文泉驿微米黑fc-list也查得到配置也写对了就是不显示。最后发现他把字体安装在~/.fonts目录但这个目录在系统字体缓存里的优先级太高了导致一个老版本的字体文件覆盖了新版本而老版本文件本身损坏了。解决方法是删除~/.fonts下的旧文件从系统包管理器重新安装中文字体再清matplotlib缓存重启问题迎刃而解。注意很多人在解决中文不显示时只关注“有没有配置”忽略了“配置之前系统环境到底支不支持”。排查的顺序必须是系统字体 → matplotlib缓存 → rcParams配置这个顺序千万别颠倒。6. 常见问题排查速查表与独家避坑经验为了方便大家快速定位我把实践中遇到的各类情况整理成了表格建议截图留存。碰到问题先对照表格判断属于哪个类型再有的放矢去解决现象根本原因解决思路中文全部显示为方框matplotlib选中了不含中文字形的字体安装中文字体并把中文字体名加入font.sans-serif中文显示为空白/消失中文字体存在但matplotlib缓存未更新清除~/.cache/matplotlib缓存重启Python进程中文显示为乱码符号字体文件损坏或字符集不匹配重新安装字体包或改用Noto CJK字体坐标轴负号显示为方块中文字体不含Unicode负号字形设置axes.unicode_minusFalsefc-list :langzh为空系统压根没有中文字体安装中文字体包重点查发行版软件源配置写对了但只部分中文显示所选字体字形覆盖不全换成覆盖面更广的字体如Noto Sans CJK SC远程服务器上图片里中文正常但本地预览乱本地查看器字体缺失更新本地系统字体或把图片转成PNG后再查看6.1 同一台机器上中英文混排怎么处理如果你画的图里有一部分标签是英文、一部分是中文需要注意matplotlib的字体回退逻辑它会在font.sans-serif列表中按顺序查找找到第一个能覆盖当前字符的字体。比如列表写成[DejaVu Sans, WenQuanYi Micro Hei]当matplotlib渲染英文时DejaVu Sans就能覆盖就用DejaVu渲染中文时DejaVu Sans覆盖不了就自动回退到下一个含中文字形的字体。这个特性的坑在于有些中文字体对英文字母的字形设计实在不敢恭维整体会显得突兀。我的经验是把最常见的无衬线西文字体放在列表最前面把中文字体放在第二位中文字体只负责兜底中文部分。比如plt.rcParams[font.sans-serif] [DejaVu Sans, WenQuanYi Micro Hei]这样英文用DejaVu中文用文泉驿两种字符各取所需排版上最接近专业图表的观感。实践下来这样的组合在处理大部分中英文混排图表时都不需要再做额外的手工调整。6.2 服务器环境下没有GUI、无法预览怎么办在纯服务器环境下画出的图片不会弹窗显示只能保存为文件再下载查看。这时常常遇到一个更具隐蔽性的问题matplotlib保存图片时默认不嵌入字体而是引用字体名称如果查看图片的机器上没有对应的中文字体图片里中文就会失效或模糊。解决方案是把图片格式换成PNG或者用dpi参数据实设置较高分辨率plt.savefig(output.png, dpi300, bbox_inchestight)这里bbox_inchestight的作用是自动裁剪图片边缘的空白区域避免因为中文标签宽度不同导致图片留有过多白边。有次处理一个宽度差异很大的标题时我就栽在没加这个参数上图片右半边留了一大块空白改完之后清爽很多。如果确实需要PDF格式建议在savefig时指定fonttypeType42这样PDF会内嵌字体轮廓跨设备查看时中文不会变成一堆乱码。6.3 “明明都配置对了换了机器又不行”的头疼时刻团队协作中最容易遇到的问题在一个机器上配好的环境能正常画中文换一台机器重新克隆代码中文又乱了。原因是每台机器的系统字体目录和软件包版本可能不一致你代码里写的字体名在那台机器上根本不存在。要根治这个问题建议在项目里做“字体自包含”把ttf或ttc字体文件直接放进项目的assets/fonts目录在代码里通过统一函数注册import os from matplotlib import font_manager def setup_chinese_font(): font_path os.path.join(os.path.dirname(__file__), assets, fonts, wqy-microhei.ttc) font_manager.fontManager.addfont(font_path) font_name font_manager.FontProperties(fnamefont_path).get_name() plt.rcParams[font.sans-serif] [font_name, DejaVu Sans] plt.rcParams[axes.unicode_minus] False这里用FontProperties(fnamefont_path).get_name()动态读取字体名称就不用硬编码字体内部名彻底摆脱对系统字体环境的依赖。我第一次用这个方法时把字体文件塞进代码库后困扰许久的跨机器中文显示问题就再没出现过。代价是仓库体积会变大几MB对绝大多数项目来说完全可接受。7. 收尾分享我用顺手的中文字体组合与几点个人心得最后想说的是中文字体不显示的问题看似简单但反复折磨人的地方在于它跨越了系统字体管理、matplotlib缓存机制、字符编码三层体系任何一个环节掉链子都会让最终效果看起来“像坏了”。我自己从第一次踩坑到现在总结出三个最实用的心得第一个心得是检查顺序永远从系统层开始。fc-list :langzh的输出为空就装字体有了字体之后才谈matplotlib配置。先花十秒钟确认系统层比在matplotlib里反复试验各种参数高效得多。第二个心得是能写进配置文件就不要在脚本里重复设置能动态注册字体就不要依赖系统环境这两条对应到具体代码就是尽量封装一个setup_chinese_font()函数并在项目入口统一调用团队成员各抄各的脚本迟早会出错。第三个心得是别忽略axes.unicode_minusFalse这一行它在中文不显示排查中经常被漏掉导致你以为问题解决了、实际图里到处都是方块负号看上去非常业余。再分享一个小细节如果你用的是Noto Sans CJK系列matplotlib的font.sans-serif列表里填Noto Sans CJK SC比填Noto Sans CJK更准确前者明确指定简体中文子集后者是泛指的整套家族。具体名称仍然以fc-list :langzh的输出来确定不要靠猜。另外字体安装完、配置改完之后最好重新打开一个Python终端而不是在同一个交互式会话里反复重跑因为matplotlib的字体配置会在进程生命周期中被缓存同一个终端里改配置经常不会生效。这个问题的处理思路其实还能迁移到其他地方比如换用seaborn画图、在GUI程序里嵌入matplotlib画布、或者在notebook里使用中文字体——本质都是让字体进入系统的索引并被绘图库正确引用。下一次再遇到奇奇怪怪的字符显示问题不妨先按照这套“系统层排查 → 缓存刷新 → 配置校验 → 字体文件自包含”的思路走一遍多数情况下都能在十分钟内解决。如果遇到的是讨论这里没覆盖到的特殊情况也可以对照fc-list的字体覆盖范围和matplotlib官方字体文档再进一步排查通常总能在字体的“覆盖字符集”这一栏找到原因。