Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fa6e938

Browse files
committed
FIX: delay resolving automatic backend until actually needed
This is to prevent the early importing of GUI bindings in the case where the user wants to use something later in our search list, but also has a toolkit higher in our list installed which we select.
1 parent ec99c15 commit fa6e938

File tree

2 files changed

+39
-9
lines changed

2 files changed

+39
-9
lines changed

lib/matplotlib/pyplot.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,15 @@ def _copy_docstring_and_deprecators(method, func=None):
104104

105105
## Global ##
106106

107+
def _initial_switch_backend():
108+
switch_backend(dict.__getitem__(rcParams, "backend"))
109+
# Just to be safe. Interactive mode can be turned on without
110+
# calling `plt.ion()` so register it again here.
111+
# This is safe because multiple calls to `install_repl_displayhook`
112+
# are no-ops and the registered function respect `mpl.is_interactive()`
113+
# to determine if they should trigger a draw.
114+
install_repl_displayhook()
115+
107116

108117
_IP_REGISTERED = None
109118
_INSTALL_FIG_OBSERVER = False
@@ -289,9 +298,13 @@ class backend_mod(matplotlib.backend_bases._Backend):
289298
# Need to keep a global reference to the backend for compatibility reasons.
290299
# See https://github.com/matplotlib/matplotlib/issues/6092
291300
matplotlib.backends.backend = newbackend
301+
switch_backend._ever_called = True
302+
switch_backend._ever_called = False
292303

293304

294305
def _warn_if_gui_out_of_main_thread():
306+
if not switch_backend._ever_called:
307+
_initial_switch_backend()
295308
if (_get_required_interactive_framework(_backend_mod)
296309
and threading.current_thread() is not threading.main_thread()):
297310
_api.warn_external(
@@ -316,6 +329,8 @@ def draw_if_interactive(*args, **kwargs):
316329
End users will typically not have to call this function because the
317330
the interactive mode takes care of this.
318331
"""
332+
if not switch_backend._ever_called:
333+
_initial_switch_backend()
319334
return _backend_mod.draw_if_interactive(*args, **kwargs)
320335

321336

@@ -2229,15 +2244,6 @@ def polar(*args, **kwargs):
22292244
set(_interactive_bk) - {'WebAgg', 'nbAgg'})
22302245
and cbook._get_running_interactive_framework()):
22312246
dict.__setitem__(rcParams, "backend", rcsetup._auto_backend_sentinel)
2232-
# Set up the backend.
2233-
switch_backend(rcParams["backend"])
2234-
2235-
# Just to be safe. Interactive mode can be turned on without
2236-
# calling `plt.ion()` so register it again here.
2237-
# This is safe because multiple calls to `install_repl_displayhook`
2238-
# are no-ops and the registered function respect `mpl.is_interactive()`
2239-
# to determine if they should trigger a draw.
2240-
install_repl_displayhook()
22412247

22422248

22432249
################# REMAINING CONTENT GENERATED BY boilerplate.py ##############

lib/matplotlib/tests/test_backends_interactive.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import sys
1010
import time
1111
import urllib.request
12+
import textwrap
1213

1314
import pytest
1415

@@ -255,6 +256,29 @@ def test_interactive_thread_safety(env):
255256
assert proc.stdout.count("CloseEvent") == 1
256257

257258

259+
def test_lazy_auto_backend_selection():
260+
261+
def _impl():
262+
import matplotlib
263+
import matplotlib.pyplot as plt
264+
# just importing pyplot should not be enough to trigger resolution
265+
bk = dict.__getitem__(matplotlib.rcParams, 'backend')
266+
assert not isinstance(bk, str)
267+
assert not plt.switch_backend._ever_called
268+
# but actually plotting should
269+
plt.plot(5)
270+
assert plt.switch_backend._ever_called
271+
bk = dict.__getitem__(matplotlib.rcParams, 'backend')
272+
assert isinstance(bk, str)
273+
274+
proc = subprocess.run(
275+
[sys.executable, "-c",
276+
textwrap.dedent(inspect.getsource(_impl)) + "\n_impl()"],
277+
env={**os.environ, "SOURCE_DATE_EPOCH": "0"},
278+
timeout=_test_timeout, check=True,
279+
stdout=subprocess.PIPE, universal_newlines=True)
280+
281+
258282
@pytest.mark.skipif('TF_BUILD' in os.environ,
259283
reason="this test fails an azure for unknown reasons")
260284
@pytest.mark.skipif(os.name == "nt", reason="Cannot send SIGINT on Windows.")

0 commit comments

Comments
 (0)