前言:很多时候,matplot跑出来的是这种静态非交互的,如果想要可以交互,就得设定一个后端,例如
matplotlib.use('TkAgg')
Matplotlib 后端 (Backend)
Matplotlib 的设计理念是能够以多种方式输出图形,无论是显示在屏幕上、保存到文件,还是嵌入到各种用户界面(GUI)中。为了实现这一点,Mat它使用了“后端”的概念。
简单来说,后端就是 Matplotlib 用来实际渲染和显示图形的软件模块。 它负责处理从 Matplotlib 的高级绘图命令(如 plt.plot()
, plt.scatter()
)到最终图像(屏幕显示或文件输出)的转换。
Matplotlib 有两种主要类型的后端:
- 用户界面后端 (User Interface Backends):也称为交互式后端。它们允许在屏幕上显示图形,并与图形进行交互(如缩放、平移、保存等)。这些后端通常依赖于特定的 GUI 工具包,比如 Tkinter、Qt、GTK、WxWidgets 等。
- 非交互式后端 (Non-Interactive Backends):也称为硬拷贝后端。这些后端主要用于将图形保存为各种文件格式(如 PNG, PDF, SVG, JPG),而不显示在屏幕上。它们不需要 GUI 工具包的支持。
如何选择后端?
Matplotlib 会尝试自动选择一个合适的后端,通常会根据安装的 GUI 库和运行环境来决定。但也可以显式地指定后端,最常用的方法是:
Python
import matplotlib
matplotlib.use('backend_name')
import matplotlib.pyplot as plt
注意:matplotlib.use()
必须在 import matplotlib.pyplot as plt
之前调用。
事件循环 (Event Loop)
事件循环是 GUI 应用程序的核心机制。它是一个持续运行的循环,负责监听来自操作系统和用户的各种事件(如鼠标点击、键盘输入、窗口调整大小、绘图刷新请求等),然后将这些事件分派给相应的处理函数。
在 Matplotlib 中,交互式后端需要一个事件循环来:
- 显示图形窗口: 只有事件循环在运行,图形窗口才能被创建和显示。
- 响应用户交互: 缩放、平移、保存等操作都需要事件循环来捕获并处理。
- 刷新和更新图形: 当图形内容发生变化时,事件循环会负责调度重绘操作。
如果在一个非 GUI 环境(如纯 Python 脚本)中使用交互式后端,并且没有显式地启动事件循环,那么图形窗口可能不会显示,或者程序会立即退出,因为没有东西来保持它运行和响应事件。
交互式后端 (Interactive Backends)
交互式后端主要用于在屏幕上显示图形并允许用户进行实时操作。
常见交互式后端:
'TkAgg'
: 基于 Tkinter GUI 工具包。它通常是 Python 安装自带的,因此在许多系统上默认可用。在的原始代码中使用的就是它。'QtAgg'
/'Qt5Agg'
/'Qt4Agg'
: 基于 Qt GUI 工具包(PyQt 或 PySide)。提供更现代、功能更丰富的 GUI。'WxAgg'
: 基于 WxPython GUI 工具包。'GTK3Agg'
/'GTK4Agg'
: 基于 GTK GUI 工具包。'macosx'
(macOS) /'webagg'
(Web) 等。
特点:
- 需要 GUI 工具包: 必须安装相应的 GUI 库才能使用。
- 需要事件循环: 要使图形窗口持续显示和响应,需要一个正在运行的事件循环。在交互式 Python 会话(如 IPython 或 Jupyter Notebook)中,事件循环通常会自动启动或以某种方式集成。但在独立脚本中,可能需要手动调用
plt.show()
,这会启动一个简单的事件循环并阻塞程序,直到窗口关闭。 - 内存管理: 由于需要维护 GUI 状态和图形对象,这些后端可能会在内存中保留更多资源。如果在循环中重复创建和关闭图形而不让事件循环充分清理,就容易导致内存泄漏或累积。
plt.close()
尝试关闭图形,但底层 GUI 库的资源清理可能不是立即的或完全的,尤其是在没有活跃事件循环的情况下。
非交互式后端 (Non-Interactive Backends)
非交互式后端不显示图形窗口,它们的主要目的是将图形直接渲染到文件或内存缓冲区中。它们通常被称为“硬拷贝”后端,因为它们生成的是最终的、不可交互的图像。
常见非交互式后端:
'Agg'
: Matplotlib 的默认非交互式后端,也是最常用的。它使用 Anti-Grain Geometry (AGG) 库来生成位图(raster)图像,如 PNG、JPEG。'PDF'
: 生成 PDF 文件。'SVG'
: 生成可缩放矢量图形 (SVG) 文件。'PS'
/'EPS'
: 生成 PostScript 或 Encapsulated PostScript 文件。
特点:
- 不显示窗口: 不会打开任何图形窗口。
- 不需要 GUI 工具包: 它们通常不依赖于任何外部 GUI 库,因此在没有桌面环境的服务器或批量处理脚本中非常有用。
- 不涉及事件循环: 由于没有 GUI 窗口,也就没有事件循环的需求。绘图完成后,图形资源可以直接被释放。
- 内存管理: 相对于交互式后端,非交互式后端在内存管理上通常更简洁和高效。它们创建图形对象,渲染到文件,然后可以更快、更彻底地释放内存。在批量生成大量图片时,使用非交互式后端能显著减少内存堆积的问题。
一个内存释放的问题
在批量处理数据并生成大量图片。如果使用的是 'TkAgg'
这样的交互式后端,即使调用了 plt.close()
,也可能发生以下情况:
- GUI 资源未及时释放:
'TkAgg'
会与 Tkinter 库交互。尽管plt.close()
关闭了 Matplotlib 的 Figure 对象,但 Tkinter 内部可能仍然保留了一些相关的窗口句柄、上下文或像素缓冲区等资源,这些资源在没有 Tkinter 事件循环积极运行时,可能不会立即被垃圾回收或释放给操作系统。 - 事件循环缺失: 在你的独立脚本中,没有一个持久运行的 Tkinter 事件循环来处理这些底层资源的清理任务。每次
extract_hef_features
函数执行完毕,它可能只是短暂地创建并关闭了一个 Tkinter 窗口,但 Tkinter 的内部状态可能没有完全重置。 - Python GC 的惰性: Python 的垃圾回收器在某些情况下可能会显得“懒惰”,它不会在内存达到临界点之前或在资源被“完全”释放(在 GUI 后端中可能需要事件循环的参与)之前主动回收内存。
解决方案:
将后端从 TkAgg
切换到 Agg
:
Agg
是为离屏渲染(off-screen rendering)设计的,它不涉及任何 GUI 窗口或事件循环。它直接将图形绘制到内存缓冲区中,然后保存到文件。Agg
的设计使其在渲染完成后,相关的内存资源可以更直接、更迅速地被 Matplotlib 和 Python 垃圾回收器识别并释放。它避免了与外部 GUI 库(如 Tkinter)的复杂交互和它们可能存在的内存管理问题。
另外还有涉及到MNE可视化后端的问题,可以看这个
MNE后端循环