Skip to content

Commit aaf140e

Browse files
authored
Merge pull request #15504 from anntzer/new_figure_in_main_thread
Warn when trying to start a GUI event loop out of the main thread.
2 parents e10e87c + ad90166 commit aaf140e

File tree

1 file changed

+50
-18
lines changed

1 file changed

+50
-18
lines changed

lib/matplotlib/pyplot.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
import re
2727
import sys
2828
import time
29+
try:
30+
import threading
31+
except ImportError:
32+
import dummy_threading as threading
2933

3034
from cycler import cycler
3135
import matplotlib
@@ -175,6 +179,11 @@ def findobj(o=None, match=None, include_self=True):
175179
return o.findobj(match, include_self=include_self)
176180

177181

182+
def _get_required_interactive_framework(backend_mod):
183+
return getattr(
184+
backend_mod.FigureCanvas, "required_interactive_framework", None)
185+
186+
178187
def switch_backend(newbackend):
179188
"""
180189
Close all open figures and set the Matplotlib backend.
@@ -188,6 +197,8 @@ def switch_backend(newbackend):
188197
newbackend : str
189198
The name of the backend to use.
190199
"""
200+
global _backend_mod
201+
191202
close("all")
192203

193204
if newbackend is rcsetup._auto_backend_sentinel:
@@ -210,15 +221,17 @@ def switch_backend(newbackend):
210221
rcParamsOrig["backend"] = "agg"
211222
return
212223

224+
# Backends are implemented as modules, but "inherit" default method
225+
# implementations from backend_bases._Backend. This is achieved by
226+
# creating a "class" that inherits from backend_bases._Backend and whose
227+
# body is filled with the module's globals.
228+
213229
backend_name = cbook._backend_module_name(newbackend)
214-
backend_mod = importlib.import_module(backend_name)
215-
Backend = type(
216-
"Backend", (matplotlib.backend_bases._Backend,), vars(backend_mod))
217-
_log.debug("Loaded backend %s version %s.",
218-
newbackend, Backend.backend_version)
219230

220-
required_framework = getattr(
221-
Backend.FigureCanvas, "required_interactive_framework", None)
231+
class backend_mod(matplotlib.backend_bases._Backend):
232+
locals().update(vars(importlib.import_module(backend_name)))
233+
234+
required_framework = _get_required_interactive_framework(backend_mod)
222235
if required_framework is not None:
223236
current_framework = cbook._get_running_interactive_framework()
224237
if (current_framework and required_framework
@@ -228,23 +241,42 @@ def switch_backend(newbackend):
228241
"framework, as {!r} is currently running".format(
229242
newbackend, required_framework, current_framework))
230243

231-
# Update both rcParams and rcDefaults so restoring the defaults later with
232-
# rcdefaults won't change the backend. A bit of overkill as 'backend' is
233-
# already in style.core.STYLE_BLACKLIST, but better to be safe.
234-
rcParams['backend'] = rcParamsDefault['backend'] = newbackend
244+
_log.debug("Loaded backend %s version %s.",
245+
newbackend, backend_mod.backend_version)
235246

236-
global _backend_mod, new_figure_manager, draw_if_interactive, _show
247+
rcParams['backend'] = rcParamsDefault['backend'] = newbackend
237248
_backend_mod = backend_mod
238-
new_figure_manager = Backend.new_figure_manager
239-
draw_if_interactive = Backend.draw_if_interactive
240-
_show = Backend.show
249+
for func_name in ["new_figure_manager", "draw_if_interactive", "show"]:
250+
globals()[func_name].__signature__ = inspect.signature(
251+
getattr(backend_mod, func_name))
241252

242253
# Need to keep a global reference to the backend for compatibility reasons.
243254
# See https://github.com/matplotlib/matplotlib/issues/6092
244255
matplotlib.backends.backend = newbackend
245256

246257

247-
def show(*args, **kw):
258+
def _warn_if_gui_out_of_main_thread():
259+
if (_get_required_interactive_framework(_backend_mod)
260+
and threading.current_thread() is not threading.main_thread()):
261+
cbook._warn_external(
262+
"Starting a Matplotlib GUI outside of the main thread will likely "
263+
"fail.")
264+
265+
266+
# This function's signature is rewritten upon backend-load by switch_backend.
267+
def new_figure_manager(*args, **kwargs):
268+
"""Create a new figure manager instance."""
269+
_warn_if_gui_out_of_main_thread()
270+
return _backend_mod.new_figure_manager(*args, **kwargs)
271+
272+
273+
# This function's signature is rewritten upon backend-load by switch_backend.
274+
def draw_if_interactive(*args, **kwargs):
275+
return _backend_mod.draw_if_interactive(*args, **kwargs)
276+
277+
278+
# This function's signature is rewritten upon backend-load by switch_backend.
279+
def show(*args, **kwargs):
248280
"""
249281
Display all figures.
250282
@@ -263,8 +295,8 @@ def show(*args, **kw):
263295
This is experimental, and may be set to ``True`` or ``False`` to
264296
override the blocking behavior described above.
265297
"""
266-
global _show
267-
return _show(*args, **kw)
298+
_warn_if_gui_out_of_main_thread()
299+
return _backend_mod.show(*args, **kwargs)
268300

269301

270302
def isinteractive():

0 commit comments

Comments
 (0)