matplotlib / matplotlib/ipympl

[Bug]: Intermittent `ValueError: The passed figure is not managed by pyplot` with ipympl (`%matplotlib widget`)

オープン
#622 コメント 13 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

主要言語
Jupyter Notebook
スター
1.7k
フォーク
234
PR マージ指標
30日以内にマージされた PR はありません

説明

Bug summary

When using Matplotlib with ipympl (%matplotlib widget) in JupyterLab, I intermittently get:

ValueError: The passed figure is not managed by pyplot

This happens with standard pyplot usage (plt.figure, plt.subplots(num=fig), plt.sca(ax)), without explicitly constructing canvases.

Code for reproduction
%matplotlib widget
import time
import matplotlib.pyplot as plt

def try_once():
    fig_num = 1
    plt.close(fig_num)
    fig = plt.figure(fig_num)

    fig, axs = plt.subplots(10, 10, num=fig, squeeze=False)

    for ax in axs.flat:
        plt.sca(ax)  # <-- intermittently raises
        plt.plot([1, 2, 3])
        fig.canvas.draw_idle()
        time.sleep(0.001)

for i in range(300):
    try:
        try_once()
    except Exception as e:
        print('FAILED at iteration', i, '->', repr(e))
        raise
Actual outcome

Image

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[2], line 20
     18 for i in range(300):
     19     try:
---> 20         try_once()
     21     except Exception as e:
     22         print('FAILED at iteration', i, '->', repr(e))

Cell In[2], line 13, in try_once()
     10 fig, axs = plt.subplots(10, 10, num=fig, squeeze=False)
     12 for ax in axs.flat:
---> 13     plt.sca(ax)  # <-- intermittently raises
     14     plt.plot([1, 2, 3])
     15     fig.canvas.draw_idle()

File ~/ipympl-debug/issue/.venv/lib/python3.12/site-packages/matplotlib/pyplot.py:1376, in sca(ax)
   1372 # Mypy sees ax.figure as potentially None,
   1373 # but if you are calling this, it won't be None
   1374 # Additionally the slight difference between `Figure` and `FigureBase` mypy catches
   1375 fig = ax.get_figure(root=False)
-> 1376 figure(fig)  # type: ignore[arg-type]
   1377 fig.sca(ax)

File ~/ipympl-debug/issue/.venv/lib/python3.12/site-packages/matplotlib/pyplot.py:995, in figure(num, figsize, dpi, facecolor, edgecolor, frameon, FigureClass, clear, **kwargs)
    993 root_fig = num.get_figure(root=True)
    994 if root_fig.canvas.manager is None:
--> 995     raise ValueError("The passed figure is not managed by pyplot")
    996 elif (any(param is not None for param in [figsize, dpi, facecolor, edgecolor])
    997       or not frameon or kwargs) and root_fig.canvas.manager.num in allnums:
    998     _api.warn_external(
    999         "Ignoring specified arguments in this call because figure "
   1000         f"with num: {root_fig.canvas.manager.num} already exists")

ValueError: The passed figure is not managed by pyplot
Expected outcome

Mosaicked plots will be shown without errors.

Additional information
Observed state at failure

At the moment the exception occurs:

  • root_fig.canvas.manager is None
  • but the corresponding FigureManager still exists in matplotlib._pylab_helpers.Gcf (so pyplot still manages the figure)

So pyplot’s “managed figure” check is making a false negative.

Where the exception comes from

plt.sca(ax) calls pyplot.figure(fig) internally.
When passed a Figure, pyplot.figure() currently checks only:

root_fig = num.get_figure(root=True)
if root_fig.canvas.manager is None:
    raise ValueError("The passed figure is not managed by pyplot")

In this bug, canvas.manager is temporarily/incorrectly None even though the manager still exists in Gcf.

Proposed fix

If root_fig.canvas.manager is None, fall back to a Gcf lookup to find the manager whose m.canvas.figure is root_fig, and reattach it.

manager = root_fig.canvas.manager
if manager is None:
    for m in _pylab_helpers.Gcf.get_all_fig_managers():
        if getattr(getattr(m, "canvas", None), "figure", None) is root_fig:
            manager = m
            root_fig.canvas.manager = m
            break
if manager is None:
    raise ValueError(...)

This same logic eliminates the failures in my environment (0/60) when applied as a small monkey patch around pyplot.figure().

This resembles matplotlib/matplotlib#19380 (same structural cause: FigureCanvasBase.__init__ sets self.manager=None after replacing figure.canvas).

Operating system

Ubuntu 24

Matplotlib Version

3.10.8

ipympl Version

0.10.0

Matplotlib Backend

widget

Python version

Python 3.12.3

Jupyter version

jupyter lab --version: 4.5.4

Installation

uv

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

まず、提供されている JupyterLab の再現手順を widget backend で実行します。次に、plt.sca(ax) を介して到達する matplotlib.pyplot.figure() と、レポートで説明されている Gcf マネージャーを調べます。繰り返しのモザイクプロットが ValueError なしで完了し、pyplot が既存の figure manager を認識すれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
jupyter, jupyter-notebook, matplotlib, python
領域
data-visualization, frontend
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
停滞
明瞭さ
明確に書かれている
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。