HOWTO · Matplotlib

Matplotlib 中 cla()、clf() 與 close() 函式的差異

本文說明如何依要處理的物件選擇 pyplot 函式:matplotlib.pyplot.cla() 清除目前的 Axes,matplotlib.pyplot.clf() 清除目前的 Figure,而 matplotlib.pyplot.close() 關閉 Figure。

本頁內容

依你想作用的物件來選擇函式:matplotlib.pyplot.cla() 清除目前的 Axes,matplotlib.pyplot.clf() 清除目前的 Figure,matplotlib.pyplot.close() 關閉一個 Figure。它們都是 pyplot 函式,不是方法,且各自的作用範圍不同。當你只想重繪一個子圖時用 cla(),想以全新版面配置複用一個 Figure 時用 clf(),當你用完一個 Figure 時用 close()。

差異一覽

這三個函式構成作用範圍的層級。cla() 作用於最小單位,即單一 Axes。clf() 作用於包含該 Axes 的整個 Figure。close() 則更進一步,將 Figure 從 pyplot 中徹底移除。

函式 作用物件 移除的內容 保留下來的內容
plt.cla() 目前的 Axes 繪製的資料、標籤、標題、圖例及其他 Axes 內容 Axes 本身、它在版面配置中的位置、所有相鄰 Axes 以及 Figure
plt.clf() 目前的 Figure Figure 中的全部 Axes 及其內容 Figure 物件及其視窗,可供放入新的 Axes
plt.close() 由 pyplot 管理的 Figure 該 Figure 在 pyplot 中的註冊(在互動式後端中還包括其視窗) 該 Figure 在 pyplot 中不復存在;相鄰的 Figure 不受影響

當一個 Figure 包含多個子圖時,作用範圍最為關鍵。清除一個 Axes 會讓該 Axes 保留在版面配置中,因此它的位置和其他子圖仍然可用。清除一個 Figure 會一次性移除它的全部 Axes,但 Figure 本身仍可用於新的 Axes 和繪圖。關閉一個 Figure 則結束 pyplot 對它的管理,這是三者中唯一能釋放 Figure 資源的函式。

除非先選定目標,否則這三個 pyplot 函式都作用於目前的 Axes 或 Figure。如果你已經持有 Axes 或 Figure 物件,物件層級操作 ax.clear() 和 fig.clear() 可以明確指定目標,而不必依賴目前恰好是哪一個 Axes 或 Figure。對於 close(),你可以直接傳入 Figure,例如 plt.close(fig)。

matplotlib.pyplot.cla()

cla() 清除目前 Axes 的繪製資料、座標軸標籤、標題、圖例及其他內容。Axes 本身仍留在 Figure 中的原位置,因此子圖格線保持其版面配置,每個相鄰子圖也保留其圖形。清除一個 Axes 不會把它從 fig.axes 中移除,也不會改變其相鄰子圖的位置。

這使 cla() 成為更新迴圈的合適工具:選中該 Axes,清除其內容,再把新資料繪製到同一位置。簡單的動畫和儀表板正是採用這種模式——只刷新單一面板,而 Figure 的其餘部分保持不變。

下面的範例建立兩個子圖,然後只清除第二個。印出的檢查結果表明,Figure 仍然包含兩個 Axes,第一個 Axes 仍保留其曲線,第二個 Axes 沒有曲線且標題為空。

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 100)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 3.2))
ax1.plot(x, np.sin(x))
ax1.set_title("Sine (kept)")
ax2.plot(x, np.cos(x), color="orange")
ax2.set_title("Cosine (to be cleared)")
fig.tight_layout()
plt.sca(ax2)
plt.cla()
print(f"axes={len(fig.axes)}; ax1_lines={len(ax1.lines)}; ax2_lines={len(ax2.lines)}; ax2_title={ax2.get_title()!r}")
plt.close(fig)

輸出:

axes=2; ax1_lines=1; ax2_lines=0; ax2_title=''

呼叫之前,兩個子圖都各有一條曲線:

呼叫 cla() 之前的兩個子圖:左側為正弦曲線,右側為餘弦曲線。

執行 plt.cla() 之後,第二個 Axes 變空,但仍佔據其位置,第一個子圖保持不變:

cla() 之後的同兩個子圖:正弦曲線完好無損,第二個 Axes 已變空但仍在其位置上。

當子圖的排列應保持不變、只需刷新一個 Axes 的內容時,選用 cla()。如果你的程式碼中已有 Axes 變數,ax.clear() 可以在不改變 pyplot 目前 Axes 選擇的情況下,表達同樣的 Axes 層級意圖。

matplotlib.pyplot.clf()

matplotlib.pyplot.clf() 清除目前的 Figure,包括它的全部 Axes 及其內容。它讓 Figure 物件保持可複用狀態,並且不會關閉關聯的 Figure 管理器,因此同一個 Figure——在互動式工作階段中即同一個視窗——可以從頭重新填充。不存在按 Axes 的 clf() 操作,因為該函式在 Figure 範圍上工作。

在 clf() 之後,Figure 中沒有任何 Axes,因此你必須先建立新的 Axes 才能再次繪圖;直接呼叫 plt.plot() 會為你隱含地建立一個新的 Axes。如果你開啟了多個 Figure,請在呼叫 plt.clf() 之前選定期望的那一個;fig.clear() 是物件層級的等價操作,它使目標明確。

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(8, 3.2))
axes[0].plot([0, 1, 2], [0, 1, 4])
axes[0].set_title("Line")
axes[1].bar(["A", "B", "C"], [3, 7, 5])
axes[1].set_title("Bar")
fig.tight_layout()
plt.figure(fig.number)
plt.clf()
print(f"figure_axes={len(fig.axes)}; figure_open={plt.fignum_exists(fig.number)}")
ax = fig.add_subplot(1, 1, 1)
ax.plot([0, 1, 2], [4, 1, 0], color="green")
ax.set_title("Rebuilt on the same Figure")
fig.tight_layout()
plt.close(fig)

輸出:

figure_axes=0; figure_open=True

該 Figure 起初並排包含一個折線圖和一個長條圖:

呼叫 clf() 之前的 Figure:在兩個子圖中並排包含一個折線圖和一個長條圖。

輸出確認 clf() 移除了全部 Axes(figure_axes=0),而 Figure 本身保持開啟狀態(figure_open=True)。隨後範例添加了一個新的 Axes,並在同一個 Figure 上繪製了一個完全不同的單一圖形:

clf() 並重建之後的同一個 Figure:一個新的綠色折線圖佔據了整個 Figure。

當你想讓一個 Figure 保持可用、但要從頭重建它的全部圖形或子圖版面配置時,使用 clf()。當同一 Figure 中的其他 Axes 必須保持原樣時,改用 cla()。

matplotlib.pyplot.close()

matplotlib.pyplot.close() 關閉一個由 pyplot 管理的 Figure。不帶參數呼叫時,它關閉目前的 Figure。傳入 Figure 物件、其編號或已註冊的 Figure 名稱,可以針對特定的 Figure,例如 plt.close(fig);傳入 'all' 則可一次性關閉所有 Figure。

關閉會把該 Figure 從 pyplot 的註冊表中移除,因此 plt.fignum_exists() 不再回報它,pyplot 也不再把它的內容保留在記憶體中。它與清除的一個重要區別在於:close() 之後,pyplot 中沒有可複用的 Figure 剩下;而 clf() 之後,同一個 Figure 以空狀態等待新內容。在互動式後端中,Figure 的視窗也會被銷毀,不過可見視窗的確切行為取決於後端和執行內容。

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

fig1, ax1 = plt.subplots(figsize=(5, 3.2))
ax1.plot([0, 1, 2], [0, 2, 1])
ax1.set_title("Report 1 (to be closed)")
fig1.tight_layout()
fig2, ax2 = plt.subplots(figsize=(5, 3.2))
ax2.plot([0, 1, 2], [1, 0, 3], color="purple")
ax2.set_title("Report 2 (kept open)")
fig2.tight_layout()
print(f"open_before={plt.get_fignums()}")
plt.close(fig1)
print(f"open_after={plt.get_fignums()}; fig1_open={plt.fignum_exists(fig1.number)}")
plt.close("all")

輸出:

open_before=[1, 2]
open_after=[2]; fig1_open=False

呼叫之後,只有 Figure 2 仍被註冊,它也是仍然可供顯示或儲存的那個 Figure:

close() 移除第一個 Figure 後留下的第二個 Figure:一幅標題為 Report 2 的紫色折線圖。

每當你建立了不再需要的 Figure 時——尤其是在產生大量 Figure 的指令碼和迴圈中——都應使用 close()。如果你需要複用同一個 Figure,改用 clf() 清除它;如果只需刷新一個子圖,則使用 cla() 或 ax.clear()。

記憶體與 Figure 的生命週期

這三個函式在代價上也有所不同。cla() 和 clf() 是廉價且可重複的操作:Figure 仍在 pyplot 中註冊,它的視窗和緩衝區保持存活,被丟棄的只有內容。這正是會被再次繪製的 Figure 所需要的。close() 是唯一能釋放 Figure 本身的函式,因此它也是一個不再被繪製的 Figure 唯一正確的收尾方式。

pyplot 會把建立的每個 Figure 一直註冊著,直到它被明確關閉;當註冊表變得過大時,它會發出警告。在迴圈中不斷建立 Figure 卻不關閉,會累積記憶體,並最終觸發 RuntimeWarning:

import warnings
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    for _ in range(25):
        plt.figure()
    print(f"figures_open={len(plt.get_fignums())}")
    print(f"too_many_figures_warning={any(str(w.message).startswith('More than 20 figures') for w in caught)}")
plt.close("all")
print(f"after_close_all={plt.get_fignums()}")

輸出:

figures_open=25
too_many_figures_warning=True
after_close_all=[]

在那個迴圈中,cla() 和 clf() 都幫不上忙,因為問題在於已註冊 Figure 的數量,而不是它們的內容。經驗法則是:同一個 Figure 還要繼續用,就清除;不再繼續,就關閉。每個 Figure 儲存一個檔案的批次處理工作,應在儲存後立即呼叫 plt.close(fig),這樣無論工作產生多少份報告,峰值記憶體都保持恆定。

應該使用哪個函式?

  • 重繪多面板 Figure 中的一個面板,例如在動畫或即時儀表板中:使用 cla()(或 ax.clear()),以便版面配置和其他面板得以保留。
  • 以不同的版面配置重新開始一個 Figure,同時保持同一個視窗或 Figure 物件:使用 clf()(或 fig.clear()),然後添加新的 Axes。
  • 用完了一個 Figure,尤其是在迴圈或批次指令碼中:使用 close(),傳入 Figure、其編號或 'all',以便 pyplot 釋放它。

最常見的錯誤包括:在本該用 cla() 的地方用了 clf()——這會悄無聲息地抹除 Figure 中的全部子圖,而不只是目前那一個;在錯誤的 Axes 處於目前狀態時呼叫 cla();以及期望一個已關閉的 Figure 之後仍能透過 pyplot 存取。簡而言之:用 cla() 清除一個 Axes,用 clf() 清除一個 Figure 中的全部 Axes,用 close() 為一個 Figure 收尾。