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

Skip to content

Commit 6aa49aa

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. Thanks to @anntzer for the implementation suggestion.
1 parent a35921c commit 6aa49aa

File tree

4 files changed

+54
-14
lines changed

4 files changed

+54
-14
lines changed

lib/matplotlib/pyplot.py

Lines changed: 27 additions & 13 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
@@ -202,6 +211,20 @@ def _get_required_interactive_framework(backend_mod):
202211
return getattr(
203212
backend_mod.FigureCanvas, "required_interactive_framework", None)
204213

214+
_backend_mod = None
215+
216+
217+
def _get_backend_mod():
218+
"""
219+
Ensure that a backend is selected and return it.
220+
221+
This is currently private, but may be made public in the future.
222+
"""
223+
if _backend_mod is None:
224+
# this will set the global!
225+
_initial_switch_backend()
226+
return _backend_mod
227+
205228

206229
def switch_backend(newbackend):
207230
"""
@@ -292,7 +315,7 @@ class backend_mod(matplotlib.backend_bases._Backend):
292315

293316

294317
def _warn_if_gui_out_of_main_thread():
295-
if (_get_required_interactive_framework(_backend_mod)
318+
if (_get_required_interactive_framework(_get_backend_mod())
296319
and threading.current_thread() is not threading.main_thread()):
297320
_api.warn_external(
298321
"Starting a Matplotlib GUI outside of the main thread will likely "
@@ -303,7 +326,7 @@ def _warn_if_gui_out_of_main_thread():
303326
def new_figure_manager(*args, **kwargs):
304327
"""Create a new figure manager instance."""
305328
_warn_if_gui_out_of_main_thread()
306-
return _backend_mod.new_figure_manager(*args, **kwargs)
329+
return _get_backend_mod().new_figure_manager(*args, **kwargs)
307330

308331

309332
# This function's signature is rewritten upon backend-load by switch_backend.
@@ -316,7 +339,7 @@ def draw_if_interactive(*args, **kwargs):
316339
End users will typically not have to call this function because the
317340
the interactive mode takes care of this.
318341
"""
319-
return _backend_mod.draw_if_interactive(*args, **kwargs)
342+
return _get_backend_mod().draw_if_interactive(*args, **kwargs)
320343

321344

322345
# This function's signature is rewritten upon backend-load by switch_backend.
@@ -365,7 +388,7 @@ def show(*args, **kwargs):
365388
explicitly there.
366389
"""
367390
_warn_if_gui_out_of_main_thread()
368-
return _backend_mod.show(*args, **kwargs)
391+
return _get_backend_mod().show(*args, **kwargs)
369392

370393

371394
def isinteractive():
@@ -2229,15 +2252,6 @@ def polar(*args, **kwargs):
22292252
set(_interactive_bk) - {'WebAgg', 'nbAgg'})
22302253
and cbook._get_running_interactive_framework()):
22312254
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()
22412255

22422256

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

lib/matplotlib/tests/test_backend_tk.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def test_func():
6464
def test_blit(): # pragma: no cover
6565
import matplotlib.pyplot as plt
6666
import numpy as np
67+
import matplotlib.backends.backend_tkagg # noqa
6768
from matplotlib.backends import _tkagg
6869

6970
fig, ax = plt.subplots()

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 plt._backend_mod is None
268+
# but actually plotting should
269+
plt.plot(5)
270+
assert plt._backend_mod is not None
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.")

lib/matplotlib/tests/test_rcparams.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,8 @@ def test_backend_fallback_headless(tmpdir):
497497
[sys.executable, "-c",
498498
"import matplotlib;"
499499
"matplotlib.use('tkagg');"
500-
"import matplotlib.pyplot"
500+
"import matplotlib.pyplot;"
501+
"matplotlib.pyplot.plot(42);"
501502
],
502503
env=env, check=True, stderr=subprocess.DEVNULL)
503504

0 commit comments

Comments
 (0)