目 录CONTENT

文章目录

【Python】Matplotlib 中文乱码问题排查与修复指南

EulerBlind
2025-11-07 / 0 评论 / 0 点赞 / 2 阅读 / 0 字

问题描述

在使用 matplotlib 绘制图表时,如果图表标题、坐标轴标签或图例中包含中文字符,可能会出现以下问题:

  1. 中文显示为方框□□□[][][]
  2. 中文显示为乱码:显示为其他字符或符号
  3. 字体警告信息:控制台输出字体相关的警告信息

问题原因

matplotlib 默认使用的字体(如 DejaVu Sans)不支持中文字符,当遇到中文字符时无法正确渲染,导致显示为方框或乱码。

排查步骤

1. 检查系统是否安装中文字体

# 检查系统字体列表(Linux)
fc-list :lang=zh | head -20

# 检查常见中文字体目录
ls -la /usr/share/fonts/truetype/ | grep -i -E "(noto|cjk|han|wqy|wenquan)"
ls -la /usr/share/fonts/opentype/noto/

2. 检查 matplotlib 可用的字体

import matplotlib.font_manager as fm

# 获取所有可用字体
all_fonts = [f.name for f in fm.fontManager.ttflist]

# 查找中文字体
chinese_fonts = [f for f in all_fonts if any(kw in f.lower() 
    for kw in ['noto', 'cjk', 'han', 'simhei', 'simsun', 'yahei', 
               'wenquan', 'wqy', 'arphic', 'song', 'hei'])]

print("找到的中文字体:")
for font in sorted(set(chinese_fonts)):
    print(f"  - {font}")

3. 检查当前 matplotlib 字体配置

import matplotlib.pyplot as plt

# 查看当前字体配置
print("当前字体配置:")
print(f"  font.sans-serif: {plt.rcParams['font.sans-serif']}")
print(f"  axes.unicode_minus: {plt.rcParams['axes.unicode_minus']}")

4. 测试中文显示

import matplotlib.pyplot as plt

# 简单测试
fig, ax = plt.subplots(figsize=(6, 4))
ax.text(0.5, 0.5, '测试中文显示', ha='center', va='center', fontsize=16)
ax.set_title('中文标题测试')
plt.savefig('/tmp/test_chinese.png', dpi=100, bbox_inches='tight')
plt.close()

解决方案

方案一:自动检测并配置中文字体(推荐)

创建一个通用的字体配置函数,自动检测并设置可用的中文字体:

import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import matplotlib
import os

def setup_chinese_font():
    """
    配置 matplotlib 使用中文字体,彻底解决中文乱码问题
    """
    # 清除字体缓存(如果需要)
    cache_dir = matplotlib.get_cachedir()
    cache_file = os.path.join(cache_dir, 'fontlist-v330.json')
    if os.path.exists(cache_file):
        try:
            os.remove(cache_file)
        except:
            pass
    
    # 按优先级尝试的中文字体列表(简体中文优先)
    chinese_fonts = [
        'Noto Sans CJK SC',      # Google Noto 简体中文(推荐)
        'Noto Serif CJK SC',      # Google Noto 简体中文(衬线)
        'WenQuanYi Micro Hei',    # 文泉驿微米黑
        'WenQuanYi Zen Hei',      # 文泉驿正黑
        'AR PL UKai CN',          # 文鼎PL中楷
        'SimHei',                 # 黑体(Windows)
        'Microsoft YaHei',        # 微软雅黑(Windows)
        'SimSun',                 # 宋体(Windows)
        'STSong',                 # 华文宋体
        'STHeiti',                # 华文黑体
        'Source Han Sans SC',     # 思源黑体
    ]
    
    # 获取所有可用字体
    available_fonts = {f.name: f for f in fm.fontManager.ttflist}
    
    # 查找第一个可用的中文字体
    selected_font = None
    for font_name in chinese_fonts:
        if font_name in available_fonts:
            selected_font = font_name
            break
    
    # 如果通过名称找不到,尝试通过路径查找
    if selected_font is None:
        for font in fm.fontManager.ttflist:
            font_path_lower = font.fname.lower()
            # 检查字体路径中是否包含中文字体关键词
            if any(keyword in font_path_lower for keyword in ['noto', 'cjk', 'wqy', 'wenquan', 'arphic']):
                # 检查字体名称是否包含中文相关关键词
                font_name_lower = font.name.lower()
                if any(keyword in font_name_lower for keyword in ['cjk', 'sc', 'chinese', 'han', 'noto']):
                    selected_font = font.name
                    break
    
    # 如果还是找不到,尝试直接从系统字体目录加载
    if selected_font is None:
        system_font_dirs = [
            '/usr/share/fonts/opentype/noto',
            '/usr/share/fonts/truetype/wqy',
            '/usr/share/fonts/truetype/arphic',
            '/usr/share/fonts/opentype',
            '/usr/share/fonts/truetype',
        ]
        
        # 优先查找 Noto Sans CJK SC
        for font_dir in system_font_dirs:
            if os.path.exists(font_dir):
                for root, dirs, files in os.walk(font_dir):
                    for file in files:
                        if 'notosanscjk' in file.lower() and ('sc' in file.lower() or 'regular' in file.lower()):
                            font_path = os.path.join(root, file)
                            try:
                                # 使用字体文件路径直接设置
                                font_prop = fm.FontProperties(fname=font_path)
                                font_name = font_prop.get_name()
                                plt.rcParams['font.sans-serif'] = [font_name] + plt.rcParams['font.sans-serif']
                                print(f"✓ 已从系统路径加载中文字体: {font_name} (来自 {font_path})")
                                plt.rcParams['axes.unicode_minus'] = False
                                return font_name
                            except Exception as e:
                                continue
    
    # 设置字体
    if selected_font:
        plt.rcParams['font.sans-serif'] = [selected_font] + plt.rcParams['font.sans-serif']
        print(f"✓ 已设置中文字体: {selected_font}")
    else:
        # 如果找不到中文字体,使用默认配置并给出警告
        print("⚠ 警告: 未找到中文字体,中文可能显示为方框")
        print("   建议安装: sudo apt-get install fonts-noto-cjk")
        plt.rcParams['font.sans-serif'] = ['DejaVu Sans']
    
    # 解决负号显示问题
    plt.rcParams['axes.unicode_minus'] = False
    
    return selected_font

# 执行字体配置
setup_chinese_font()

使用方法:

在 Jupyter Notebook 或 Python 脚本的开头调用此函数:

# 在导入 matplotlib 后立即调用
import matplotlib.pyplot as plt
setup_chinese_font()

# 之后正常使用 matplotlib
fig, ax = plt.subplots()
ax.set_title('中文标题')
ax.set_xlabel('横轴标签')
ax.set_ylabel('纵轴标签')
plt.show()

方案二:手动指定字体

如果知道系统中已安装的中文字体名称,可以直接指定:

import matplotlib.pyplot as plt

# 方法1:通过字体名称
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'WenQuanYi Micro Hei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

# 方法2:通过字体文件路径
from matplotlib.font_manager import FontProperties
font_prop = FontProperties(fname='/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
plt.rcParams['font.sans-serif'] = [font_prop.get_name()]

方案三:安装中文字体(如果系统没有)

Ubuntu/Debian 系统

# 安装文泉驿字体
sudo apt-get update
sudo apt-get install fonts-wqy-microhei fonts-wqy-zenhei

# 或安装 Noto 字体(推荐)
sudo apt-get install fonts-noto-cjk

# 安装后清除 matplotlib 字体缓存
rm -rf ~/.cache/matplotlib

CentOS/RHEL 系统

# 安装文泉驿字体
sudo yum install wqy-microhei-fonts wqy-zenhei-fonts

# 或安装 Noto 字体
sudo yum install google-noto-cjk-fonts

macOS 系统

macOS 系统通常自带中文字体,如果遇到问题:

# 清除 matplotlib 字体缓存
rm -rf ~/.matplotlib

Windows 系统

Windows 系统通常自带中文字体(如 SimHei、SimSun、Microsoft YaHei),如果遇到问题:

# 直接使用系统字体
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'SimSun']
plt.rcParams['axes.unicode_minus'] = False

方案四:清除字体缓存并重建

如果字体已安装但 matplotlib 仍无法识别:

import matplotlib
import matplotlib.font_manager as fm
import os

# 清除字体缓存
cache_dir = matplotlib.get_cachedir()
cache_file = os.path.join(cache_dir, 'fontlist-v330.json')
if os.path.exists(cache_file):
    os.remove(cache_file)

# 重建字体缓存(需要重启 Python 内核或重新导入)
# 在 Jupyter 中需要重启内核

或者使用命令行:

# 清除 matplotlib 缓存
rm -rf ~/.cache/matplotlib
# 或
rm -rf ~/.matplotlib

完整示例

Jupyter Notebook 中的使用

# Cell 1: 导入库并配置字体
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import matplotlib
import os

def setup_chinese_font():
    """配置中文字体"""
    chinese_fonts = [
        'Noto Sans CJK SC',
        'WenQuanYi Micro Hei',
        'AR PL UKai CN',
    ]
    
    available_fonts = {f.name: f for f in fm.fontManager.ttflist}
    
    for font_name in chinese_fonts:
        if font_name in available_fonts:
            plt.rcParams['font.sans-serif'] = [font_name]
            plt.rcParams['axes.unicode_minus'] = False
            print(f"✓ 已设置中文字体: {font_name}")
            return font_name
    
    print("⚠ 未找到中文字体")
    plt.rcParams['axes.unicode_minus'] = False
    return None

setup_chinese_font()

# Cell 2: 绘制图表
import numpy as np
import pandas as pd

fig, axes = plt.subplots(2, 2, figsize=(15, 10))

# 示例数据
scores = [1, 2, 3, 4, 3, 4, 2, 3, 4, 4]

# 1. 直方图
axes[0, 0].hist(scores, bins=20, edgecolor='black', alpha=0.7)
axes[0, 0].set_title('评分分布(直方图)', fontsize=12)
axes[0, 0].set_xlabel('分数')
axes[0, 0].set_ylabel('频数')
axes[0, 0].grid(True, alpha=0.3)

# 2. 箱线图
axes[0, 1].boxplot(scores, vert=True)
axes[0, 1].set_title('评分分布(箱线图)', fontsize=12)
axes[0, 1].set_ylabel('分数')
axes[0, 1].grid(True, alpha=0.3)

# 3. 条形图
score_counts = pd.Series(scores).value_counts().sort_index()
axes[1, 0].bar(score_counts.index, score_counts.values, alpha=0.7, edgecolor='black')
axes[1, 0].set_title('评分计数', fontsize=12)
axes[1, 0].set_xlabel('分数')
axes[1, 0].set_ylabel('数量')
axes[1, 0].grid(True, alpha=0.3, axis='y')

# 4. 累积分布
sorted_scores = np.sort(scores)
cumulative = np.arange(1, len(sorted_scores) + 1) / len(sorted_scores)
axes[1, 1].plot(sorted_scores, cumulative, marker='o', markersize=2)
axes[1, 1].set_title('评分累积分布', fontsize=12)
axes[1, 1].set_xlabel('分数')
axes[1, 1].set_ylabel('累积概率')
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

常见问题

Q1: 字体已安装但 matplotlib 仍无法识别

解决方案:

  1. 清除 matplotlib 字体缓存:

    rm -rf ~/.cache/matplotlib
    
  2. 重启 Python 内核或重新导入 matplotlib

  3. 使用字体文件路径直接加载(见方案二)

Q2: 在 Jupyter Notebook 中设置后仍显示乱码

解决方案:

  1. 确保在第一个 Cell 中设置字体配置
  2. 重启 Jupyter 内核
  3. 检查是否在多个 Cell 中重复设置导致冲突

Q3: 负号显示为方框

解决方案:

plt.rcParams['axes.unicode_minus'] = False

Q4: 不同操作系统字体名称不同

解决方案:

使用自动检测函数(方案一),它会自动适配不同操作系统。

Q5: 使用 seaborn 时中文仍显示乱码

解决方案:

seaborn 基于 matplotlib,设置 matplotlib 的字体配置即可:

import matplotlib.pyplot as plt
import seaborn as sns

# 先设置 matplotlib 字体
setup_chinese_font()

# 然后使用 seaborn
sns.set_style("whitegrid")
# seaborn 会自动使用 matplotlib 的字体配置

最佳实践

  1. 统一配置:在项目开头统一配置字体,避免在每个脚本中重复设置
  2. 自动检测:使用自动检测函数,提高代码的可移植性
  3. 字体优先级:优先使用 Noto Sans CJK SC,它是 Google 开发的高质量中文字体
  4. 缓存管理:安装新字体后记得清除 matplotlib 缓存
  5. 错误处理:在字体配置函数中添加错误处理,避免因字体问题导致程序崩溃

总结

matplotlib 中文乱码问题的核心是字体配置。通过以下步骤可以彻底解决:

  1. 排查:检查系统字体和 matplotlib 字体列表
  2. 安装:如果缺少中文字体,安装合适的字体包
  3. 配置:使用自动检测函数或手动指定字体
  4. 验证:绘制测试图表确认中文正常显示

使用本文提供的自动检测函数(方案一)是最推荐的解决方案,它能够自动适配不同环境,提高代码的可移植性和健壮性。

0
博主关闭了所有页面的评论