Skip to content

FIX: don't close figures if switch_backend is a no-op #14471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,10 @@ def use(backend, *, force=True):
"""
Select the backend used for rendering and GUI integration.

If pyplot is already imported, `~matplotlib.pyplot.switch_backend` is used
and if the new backend is different than the current backend, all Figures
will be closed.

Parameters
----------
backend : str
Expand Down Expand Up @@ -1135,6 +1139,8 @@ def use(backend, *, force=True):
--------
:ref:`backends`
matplotlib.get_backend
matplotlib.pyplot.switch_backend

"""
name = validate_backend(backend)
# don't (prematurely) resolve the "auto" backend setting
Expand Down
19 changes: 13 additions & 6 deletions lib/matplotlib/pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,21 +209,24 @@ def _get_backend_mod():

def switch_backend(newbackend):
"""
Close all open figures and set the Matplotlib backend.
Set the pyplot backend.

The argument is case-insensitive. Switching to an interactive backend is
possible only if no event loop for another interactive backend has started.
Switching to and from non-interactive backends is always possible.
Switching to an interactive backend is possible only if no event loop for
another interactive backend has started. Switching to and from
non-interactive backends is always possible.

If the new backend is different than the current backend then all open
Figures will be closed via ``plt.close('all')``.

Parameters
----------
newbackend : str
The name of the backend to use.
The case-insensitive name of the backend to use.

"""
global _backend_mod
# make sure the init is pulled up so we can assign to it later
import matplotlib.backends
close("all")

if newbackend is rcsetup._auto_backend_sentinel:
current_framework = cbook._get_running_interactive_framework()
Expand Down Expand Up @@ -260,6 +263,8 @@ def switch_backend(newbackend):
switch_backend("agg")
rcParamsOrig["backend"] = "agg"
return
# have to escape the switch on access logic
old_backend = dict.__getitem__(rcParams, 'backend')

backend_mod = importlib.import_module(
cbook._backend_module_name(newbackend))
Expand Down Expand Up @@ -323,6 +328,8 @@ def draw_if_interactive():
# Need to keep a global reference to the backend for compatibility reasons.
# See https://github.com/matplotlib/matplotlib/issues/6092
matplotlib.backends.backend = newbackend
if not cbook._str_equal(old_backend, newbackend):
close("all")

# make sure the repl display hook is installed in case we become
# interactive
Expand Down
11 changes: 11 additions & 0 deletions lib/matplotlib/tests/test_pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,14 @@ def test_minor_ticks():
tick_labels = ax.get_yticklabels(minor=True)
assert np.all(tick_pos == np.array([3.5, 6.5]))
assert [l.get_text() for l in tick_labels] == ['a', 'b']


def test_switch_backend_no_close():
plt.switch_backend('agg')
fig = plt.figure()
fig = plt.figure()
assert len(plt.get_fignums()) == 2
plt.switch_backend('agg')
assert len(plt.get_fignums()) == 2
plt.switch_backend('svg')
assert len(plt.get_fignums()) == 0