前言:很多時候,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后端循環