From 904d0934993cfd2aa116108908f2ff6216d296e5 Mon Sep 17 00:00:00 2001 From: Aaratrika-Shelly Date: Tue, 3 Mar 2026 10:05:14 +0000 Subject: [PATCH] API: Raise ValueError in subplots if num refers to existing figure --- .../next_api_changes/behavior/31223-AS.rst | 28 +++++++++++++ lib/matplotlib/pyplot.py | 25 ++++++++++++ lib/matplotlib/tests/test_pyplot.py | 39 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 doc/api/next_api_changes/behavior/31223-AS.rst diff --git a/doc/api/next_api_changes/behavior/31223-AS.rst b/doc/api/next_api_changes/behavior/31223-AS.rst new file mode 100644 index 000000000000..0880659e55ee --- /dev/null +++ b/doc/api/next_api_changes/behavior/31223-AS.rst @@ -0,0 +1,28 @@ +``pyplot.subplot`` and ``pyplot.subplot_mosaic`` raise *ValueError* on existing figures +---------------------------------------------------------------------------------------- + +Passing a *num* argument to `~.pyplot.subplots` or `~.pyplot.subplot_mosaic` +that refers to an existing figure or is a ``Figure`` instance now raises a +`ValueError`. + +These utility functions are intended strictly for the creation of new figures and +subplots. Previously, they accidentally allowed the reuse of existing figures because +they internally called `~.pyplot.figure`. This change ensures that these functions +strictly follow their documented purpose of creating new figures. + +To reuse an existing figure, clear it first using ``clear=True``: + +.. code-block:: python + + fig, axs = plt.subplots(num=1, clear=True) + # or + fig, axd = plt.subplot_mosaic([['A', 'B']], num=1, clear=True) + +If you have a ``Figure`` instance and want to add subplots to it, use the +object-oriented API: + +.. code-block:: python + + fig.subplots(nrows=2, ncols=2) + # or + fig.subplot_mosaic([['A', 'B']]) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 9cec08a15c16..483bc031336c 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1178,6 +1178,25 @@ def fignum_exists(num: int | str) -> bool: ) +def _raise_if_figure_exists(num, func_name, clear=False): + """ + Raise a ValueError if the figure *num* already exists. + """ + if num is not None and not clear: + if isinstance(num, FigureBase): + raise ValueError( + f"num {num!r} cannot be a FigureBase instance. " + f"plt.{func_name}() is for creating new figures. " + f"To add to an existing figure, use fig.{func_name}() " + "instead.") + + if fignum_exists(num): + raise ValueError( + f"Figure {num!r} already exists. Use plt.figure({num!r}) " + f"to get it or plt.close({num!r}) to close it. " + f"Alternatively, pass 'clear=True' to {func_name}().") + + def get_fignums() -> list[int]: """Return a list of existing figure numbers.""" return sorted(_pylab_helpers.Gcf.figs) @@ -1856,6 +1875,9 @@ def subplots( fig, ax = plt.subplots(num=10, clear=True) """ + num = fig_kw.get('num') + _raise_if_figure_exists(fig_kw.get('num'), "subplots", fig_kw.get('clear')) + fig = figure(**fig_kw) axs = fig.subplots(nrows=nrows, ncols=ncols, sharex=sharex, sharey=sharey, squeeze=squeeze, subplot_kw=subplot_kw, @@ -2029,6 +2051,9 @@ def subplot_mosaic( total layout. """ + num = fig_kw.get('num') + _raise_if_figure_exists(fig_kw.get('num'), "subplot_mosaic", fig_kw.get('clear')) + fig = figure(**fig_kw) ax_dict = fig.subplot_mosaic( # type: ignore[misc] mosaic, # type: ignore[arg-type] diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index 1cca7332aa0c..39ffc54bce79 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -532,3 +532,42 @@ def assert_same_signature(func1, func2): def test_setloglevel_signature(): assert_same_signature(plt.set_loglevel, mpl.set_loglevel) + + +def test_subplots_reuse_existing_figure_error(): + """Test interaction of plt.subplots(num=...) with existing figures.""" + # Create a figure with a specific number first. + fig = plt.figure(1) + + # Case 1: Reusing without clear=True should raise ValueError + with pytest.raises(ValueError, match="already exists"): + plt.subplots(num=1) + + # Case 2: Reusing WITH clear=True should work fine (no error) + fig_new, axs = plt.subplots(num=1, clear=True) + assert fig_new is fig + + # Case 3: Test passing the actual Figure object (The "Narrow Check") + with pytest.raises(ValueError, match="cannot be a FigureBase instance"): + plt.subplots(num=fig) + + plt.close(1) + + +def test_subplot_mosaic_reuse_existing_figure_error(): + """Test that plt.subplot_mosaic raises ValueError when reusing a figure.""" + fig = plt.figure(2) + + # 1. Test passing the existing figure number + with pytest.raises(ValueError, match="already exists"): + plt.subplot_mosaic([['A']], num=2) + + # 2. Test passing the actual Figure object + with pytest.raises(ValueError, match="cannot be a FigureBase instance"): + plt.subplot_mosaic([['A']], num=fig) + + # 3. Test that clear=True allows reuse without error + fig_new, axd = plt.subplot_mosaic([['A']], num=2, clear=True) + assert fig_new is fig + + plt.close(2)