
1. 先别急着下载字体文件如果你的 Mac 上已经装了 Python 和 matplotlib第一次画出带中文标签的图时大概率会看到一排排整整齐齐的方框或者干脆只显示英文、汉字全部消失。这种时候打开浏览器搜索matplotlib 中文乱码绝大多数教程会甩给你一个百度网盘链接让你下载SimHei.ttf或者微软雅黑再丢进 matplotlib 的字体目录里。我强烈不建议在 macOS 上这么做原因有几点网上下载的中文字体大多是 Windows 平台的虽然 TrueType 字体在 macOS 上也能用但直接把第三方字体塞进 matplotlib 的字体目录会让你的环境变得很“脏”。这些字体文件往往没有授权你在自己的项目里用一用也就罢了一旦要把环境复制给同事、或者部署到服务器上会引入不必要的版权和合规风险。你的 Mac 里明明带着一堆高质量中文字体为什么还要舍近求远苹果对中文字体渲染的优化是出了名的直接用系统字体不仅省事显示效果也更干净、更“苹果”。这篇文章就解决一个问题在 macOS 上不需要额外安装任何字体文件让 matplotlib 正确显示中文字体。我会讲清楚背后的原理给你几套可以“抄作业”的配置方式并且把最容易踩的坑——比如字体缓存——也一并排除掉。无论你是刚上手 Python 可视化的新手还是已经在做数据报告的老手看完应该都不会再被中文方块困扰。2. 摸清家底macOS 自带的中文字体库既然说好“不额外安装字体”那我们得先知道自己手上有哪些牌可以打。macOS 自带的中文字体其实相当丰富配合 matplotlib 使用最常用、效果也最稳定的是这几款字体名称系统文件名 / 路径示例风格特点PingFang SC苹方/System/Library/Fonts/PingFang.ttc苹果为中文用户打造的现代黑体macOS 10.11 之后成为系统默认中文字体字重多、耐看Hiragino Sans GB冬青黑体/System/Library/Fonts/Hiragino Sans GB.ttc经典的中文字体笔画干净在中文排版里出镜率很高常用于代码注释场景STHeiti华文黑体/Library/Fonts/STHeiti Light.ttc等老牌的华文字体家族兼容性不错但字形相对陈旧除了上面这些还有宋体类的STSong、楷体类的STKaiti不过做数据可视化时黑体类因为笔画均匀、无衬线所以效果最好日常图表基本用不到宋楷。你可以先用命令行的方式验证一下系统里到底有哪些中文字体。打开终端执行fc-list :langzh如果你的 Mac 上装了fontconfig通常用 Homebrew 装依赖时会顺带装上这个命令会列出所有支持中文的字体文件路径。如果你没装fontconfig也可以用system_profiler SPFontsDataType查看不过输出要啰嗦不少。一个更直接的方式是直接在 Python 里看 matplotlib 能识别到哪些字体from matplotlib import font_manager for f in font_manager.fontManager.ttflist: if PingFang in f.name or Hiragino in f.name or STHeiti in f.name: print(f.name, -, f.fname)运行之后你大概率会看到PingFang SC - /System/Library/Fonts/PingFang.ttc Hiragino Sans GB - /System/Library/Fonts/Hiragino Sans GB.ttc STHeiti - /System/Library/Fonts/STHeiti Light.ttc这行输出就是我们的底气matplotlib 本来就能看到这些字体只是默认不优先用它们而已。3. 核心原理Matplotlib 如何与中文字体“失联”在动手配置之前我得先把原理讲清楚不然你改了配置也会云里雾里下次换台电脑又抓瞎。matplotlib 加载字体时遵循一套内部优先级它首先会从matplotlib.font_manager维护的字体列表中去查找你指定的字体家族名称。如果没有指定它默认使用font.family的默认值通常是[sans-serif]而sans-serif里排第一位的默认字体是DejaVu Sans。DejaVu Sans是一款非常优秀的西文字体但它的字符集里根本没有 CJK中日韩统一表意文字的汉字字形。当 matplotlib 拿到一个数据标签比如“销售额”它会在当前字体里逐字符查找字形。对于“销”这个字DejaVu Sans里没有对应的字形映射。正常情况下系统渲染出了一个缺失字形标记——也就是你看到的小方框“豆腐块”。这里有个非常关键的认知matplotlib 并不负责渲染汉字它只负责按照字体文件里的字形信息去绘制图元。你做的所有配置本质上是让它放弃“找不到字形”的字体转而去使用系统里有汉字字形的字体。顺带提一个高频问题为什么有的图里中文变成小方块之后负号“-”也变成了方块这是因为中文字体里往往不包含英文的负号 U2212而 matplotlib 默认会把负号渲染成这个特殊字符。解决办法是手动指定axes.unicode_minus为False让它用普通的 ASCII 连字符代替。这个配置在接下来的步骤里我也会带上。还有一个概念容易被搞混字体家族名称Font Family和字体文件路径Font File的区别。font.family对应的是逻辑上的字体分类比如sans-serif、serif。font.sans-serif里的列表存的是具体的字体家族名称比如PingFang SC。而/System/Library/Fonts/PingFang.ttc是字体文件在磁盘上的位置。matplotlib 的font_manager做的事情就是把这些位置和名称注册到一个内部索引里。你在代码里写的PingFang SC只是告诉索引“我要这个家族”索引再去对应到实际的字体文件。所以只要系统字体文件存在并且 matplotlib 认识它配置就只是改一行字符串的事情。4. 实战配置三套方案从局部到全局现在进入正题。我不打算给你唯一答案而是给出三套由轻到重的方案你可以按自己的实际场景选用。4.1 方案一在代码里动态指定 rcParams最推荐这个方案适合绝大多数写脚本、跑 notebook 的场景优点是侵入性最小代码即文档看一眼就知道你做了什么也方便跟同事保持一致。关键代码就四行import matplotlib import matplotlib.pyplot as plt # 指定使用 sans-serif 字体族无衬线字体 matplotlib.rcParams[font.family] sans-serif # 在这个字体族里按顺序优先使用以下中文字体 matplotlib.rcParams[font.sans-serif] [ PingFang SC, # 苹方macOS 系统默认 Hiragino Sans GB, # 冬青黑体经典选择 STHeiti, # 华文黑体兜底 ] # 防止负号被渲染成豆腐块 matplotlib.rcParams[axes.unicode_minus] False配置完之后你画图时如果设置了中文label或title就会自动使用苹方字体。plt.plot([1, 2, 3], [4, 5, 6], label销售额) plt.title(月度销售趋势) plt.xlabel(月份) plt.ylabel(金额元) plt.legend() plt.show()这里的[PingFang SC, Hiragino Sans GB, STHeiti]是一个优先级列表。matplotlib 会从左到右查找第一个字体找不到就用第二个。这种配置方式特别适合那种“我本地能用同事的机器上也大概率能用”的场景因为这几款都是 macOS 标配。4.2 方案二用 font_manager 注册字体文件路径更稳更准如果你发现直接指定PingFang SC有时不起作用或者你在一个精简过的 macOS 环境比如 Docker 容器里运行那直接用font_manager动态注册字体文件路径会更加稳妥。from matplotlib import font_manager # 注册具体字体文件 font_manager.fontManager.addfont(/System/Library/Fonts/Hiragino Sans GB.ttc) # 注册之后字体家族名称以 addfont 后的实际名称为准 import matplotlib.pyplot as plt plt.rcParams[font.family] Hiragino Sans GB这里有个比较隐蔽的坑.ttc是苹果的字体集合文件里面可能包含多个字重和多种字体。某些版本的 matplotlib 对.ttc文件的支持不完全addfont注册后可能只识别第一个字重导致你选中了Hiragino Sans GB但加粗、斜体这些变体通通失效。我的建议是优先使用.ttf或.otf格式的字体文件。如果必须用.ttc注册后务必打印一下font_manager.fontManager.ttflist里的名称确认注册结果。4.3 方案三修改 matplotlibrc 全局配置文件一劳永逸如果你希望整个团队的新人克隆下项目后不需要任何额外配置就能跑出中文图表那建议直接修改 matplotlib 的全局配置文件也就是matplotlibrc。先找到配置文件位置import matplotlib print(matplotlib.get_configdir())通常输出~/.matplotlib。在该目录下如果没有matplotlibrc文件就在 Python 里生成一份标准模板import matplotlib matplotlib.matplotlib_fname() # 这是实际加载的配置文件路径通常在 site-packages 里不过更推荐的做法是在matplotlib.get_configdir()目录下新建一个matplotlibrc文件把你想全局覆盖的配置写进去cd ~/.matplotlib cat matplotlibrc EOF font.family: sans-serif font.sans-serif: PingFang SC, Hiragino Sans GB, STHeiti axes.unicode_minus: False EOF注意~/.matplotlib/matplotlibrc这个用户级配置会覆盖系统默认配置但不会被site-packages里的升级覆盖掉。之后你开的每一个 Python 进程、每一个 notebook都会默认加载到这个配置不用再写那几行 rcParams 了。我把三个方案的关键区别整理成表格方便你结合自己的项目选对比维度代码内 rcParamsfont_manager 注册matplotlibrc 全局配置侵入性低只影响当前代码块低但需要知道路径高影响该用户所有 matplotlib 绘图跨平台可移植性高换 Linux 也能改写法中路径需改低换机器要重新配置可维护性随代码走清晰随脚本走一人配置全局生效但新人容易忽略遇到问题时的排查难度简单中等较难容易“为什么换了机器就没字体”如果你只是自己写写分析脚本方案一就够了。如果你是团队项目比较建议方案一加一个统一的入门文档或者在项目根目录放一个set_chinese_font.py工具模块大家都从里面 import。5. 脚下有坑清掉字体缓存让改动生效这一节值得单独拿出来讲。因为很多人明明按教程改了配置还是看到方块最后差点重装 Python。Matplotlib 为了加速字体查找会在首次运行后把系统里所有字体信息缓存下来形成一个fontlist-*.json文件。问题是当你手动修改了字体配置、或者调用了addfont注册新字体之后matplotlib 可能还傻乎乎地读旧缓存根本不知道你有新字体可用。现象就是配置改好了代码重跑了重启了 Jupyter Kernel还是方块。输出里虽然没报错但字体毫无变化。解决方式很简单两步走第一步查看缓存文件import matplotlib matplotlib.get_cachedir()输出类似/Users/你的用户名/.matplotlib目录下会有fontlist-v310.json、fontlist-v330.json这样的文件。直接手动删掉rm ~/.matplotlib/fontlist-*.json第二步删除缓存之后重新运行 Python或者直接在脚本里强制重建import matplotlib matplotlib.font_manager._load_fontmanager(try_read_cacheFalse)在实际操作中如果你用了font_manager.addfont()我建议你在代码里检测到缓存存在时直接清除并重建而不是等着用户手动去删。再补一个注意事项Jupyter Notebook 的 Kernel 需要重启或者至少执行%matplotlib auto重载一下字体管理器否则旧 cache 可能还驻留在内存里。如果你是在 notebook 里反复调试字体最容易遇到“改完全局配置不生效”的情况很多时候不是配置问题而是 Kernel 没有重启。6. 收个尾封装一个 setup_chinese_font 函数当你摸清了上面的原理和坑就可以把它们固化成一个工具函数。以后每次画图前调一下干干净净不用再记那几行配置。我来提供一个可以直接复制粘贴的函数 setup_chinese_font.py 在 macOS 上无需安装额外字体自动为 matplotlib 配置中文字体。 import matplotlib from matplotlib import font_manager def setup_chinese_font(prefer: str PingFang SC) - None: 自动配置 Matplotlib 使用 macOS 自带的中文字体。 Parameters ---------- prefer : str 优先使用的字体名称默认 PingFang SC。 如果该字体不存在则自动回退到其他系统内置中文字体。 # 1. 尝试按名称直接配置方案一 available_fonts {f.name for f in font_manager.fontManager.ttflist} # 2. 如果首选字体在 matplotlib 索引里直接设置 rcParams chinese_fonts [ prefer, Hiragino Sans GB, STHeiti, STSong, ] font_to_use None for font_name in chinese_fonts: if font_name in available_fonts: font_to_use font_name break if font_to_use is not None: matplotlib.rcParams[font.family] sans-serif matplotlib.rcParams[font.sans-serif] [font_to_use] matplotlib.rcParams[axes.unicode_minus] False return # 3. 如果都没有尝试用 addfont 强制注册 .ttc 文件方案二 system_zh_fonts [ /System/Library/Fonts/PingFang.ttc, /System/Library/Fonts/Hiragino Sans GB.ttc, /Library/Fonts/STHeiti Light.ttc, ] for path in system_zh_fonts: import os if os.path.exists(path): try: font_manager.fontManager.addfont(path) # 重新构建可用字体集合 available_fonts {f.name for f in font_manager.fontManager.ttflist} for font_name in chinese_fonts: if font_name in available_fonts: font_to_use font_name break if font_to_use is not None: matplotlib.rcParams[font.family] sans-serif matplotlib.rcParams[font.sans-serif] [font_to_use] matplotlib.rcParams[axes.unicode_minus] False return except Exception: # 失败就尝试下一个路径 continue raise RuntimeError(未找到可用的系统内置中文字体请检查 macOS 字体配置。)这个函数里做了一件比较重要的事先用available_fonts集合判断字体是否已经被 matplotlib 索引。如果不是在就去扫描几个固定的系统路径用addfont强制注册。如果还不行就抛出异常提醒用户。实际的调用方式就是这样from setup_chinese_font import setup_chinese_font setup_chinese_font() import matplotlib.pyplot as plt plt.plot([1, 2, 3], [4, 5, 6], label销售额) plt.title(中文标题测试) plt.legend() plt.show()在 M 系列芯片的新 MacBook、Mac mini 上这几款字体都是系统默认存在的所以这个函数实际上在绝大多数情况下走第一步就直接 return 了非常快。最后分享一个我个人的习惯在项目里我把这个函数放进一个common/conf.py模块里所有脚本都从那里引用。这样哪怕某天某个同事换成 Windows 或者 Linux他只需要改这一个文件里的字体名称列表剩下的画图代码一行都不用动。这比你把rcParams写得到处都是要好维护得多。macOS 上确实没必要额外装字体用好系统自带的那几款黑体你的图表在观感上也会比默认的 DejaVu Sans 好看不少。下次再遇到中文方块先清缓存再喊救命。