From 268d498c0e39ec9d4e443878253d1afa3cf63238 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 24 Oct 2019 21:43:46 +0200 Subject: [PATCH 1/3] Rework exporting of backend functions in pyplot. --- lib/matplotlib/pyplot.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 239471cf75af..1263c576dcd6 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -188,6 +188,8 @@ def switch_backend(newbackend): newbackend : str The name of the backend to use. """ + global _backend_mod + close("all") if newbackend is rcsetup._auto_backend_sentinel: @@ -211,14 +213,12 @@ def switch_backend(newbackend): return backend_name = cbook._backend_module_name(newbackend) - backend_mod = importlib.import_module(backend_name) - Backend = type( - "Backend", (matplotlib.backend_bases._Backend,), vars(backend_mod)) - _log.debug("Loaded backend %s version %s.", - newbackend, Backend.backend_version) + + class backend_mod(matplotlib.backend_bases._Backend): + locals().update(vars(importlib.import_module(backend_name))) required_framework = getattr( - Backend.FigureCanvas, "required_interactive_framework", None) + backend_mod.FigureCanvas, "required_interactive_framework", None) if required_framework is not None: current_framework = cbook._get_running_interactive_framework() if (current_framework and required_framework @@ -228,23 +228,30 @@ def switch_backend(newbackend): "framework, as {!r} is currently running".format( newbackend, required_framework, current_framework)) - # Update both rcParams and rcDefaults so restoring the defaults later with - # rcdefaults won't change the backend. A bit of overkill as 'backend' is - # already in style.core.STYLE_BLACKLIST, but better to be safe. - rcParams['backend'] = rcParamsDefault['backend'] = newbackend + _log.debug("Loaded backend %s version %s.", + newbackend, backend_mod.backend_version) - global _backend_mod, new_figure_manager, draw_if_interactive, _show + rcParams['backend'] = rcParamsDefault['backend'] = newbackend _backend_mod = backend_mod - new_figure_manager = Backend.new_figure_manager - draw_if_interactive = Backend.draw_if_interactive - _show = Backend.show + for func_name in ["new_figure_manager", "draw_if_interactive", "show"]: + globals()[func_name].__signature__ = inspect.signature( + getattr(backend_mod, func_name)) # Need to keep a global reference to the backend for compatibility reasons. # See https://github.com/matplotlib/matplotlib/issues/6092 matplotlib.backends.backend = newbackend -def show(*args, **kw): +def new_figure_manager(*args, **kwargs): + """Create a new figure manager instance.""" + return _backend_mod.new_figure_manager(*args, **kwargs) + + +def draw_if_interactive(*args, **kwargs): + return _backend_mod.draw_if_interactive(*args, **kwargs) + + +def show(*args, **kwargs): """ Display all figures. @@ -263,8 +270,7 @@ def show(*args, **kw): This is experimental, and may be set to ``True`` or ``False`` to override the blocking behavior described above. """ - global _show - return _show(*args, **kw) + return _backend_mod.show(*args, **kwargs) def isinteractive(): From 5062e9ff2ca7edec03d38adc1abc788b419bf1c8 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 24 Oct 2019 21:50:42 +0200 Subject: [PATCH 2/3] Warn when trying to start a GUI event loop out of the main thread. --- lib/matplotlib/pyplot.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 1263c576dcd6..0214d4ac1b9d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -26,6 +26,10 @@ import re import sys import time +try: + import threading +except ImportError: + import dummy_threading as threading from cycler import cycler import matplotlib @@ -175,6 +179,11 @@ def findobj(o=None, match=None, include_self=True): return o.findobj(match, include_self=include_self) +def _get_required_interactive_framework(backend_mod): + return getattr( + backend_mod.FigureCanvas, "required_interactive_framework", None) + + def switch_backend(newbackend): """ Close all open figures and set the Matplotlib backend. @@ -217,8 +226,7 @@ def switch_backend(newbackend): class backend_mod(matplotlib.backend_bases._Backend): locals().update(vars(importlib.import_module(backend_name))) - required_framework = getattr( - backend_mod.FigureCanvas, "required_interactive_framework", None) + required_framework = _get_required_interactive_framework(backend_mod) if required_framework is not None: current_framework = cbook._get_running_interactive_framework() if (current_framework and required_framework @@ -242,8 +250,17 @@ class backend_mod(matplotlib.backend_bases._Backend): matplotlib.backends.backend = newbackend +def _warn_if_gui_out_of_main_thread(): + if (_get_required_interactive_framework(_backend_mod) + and threading.current_thread() is not threading.main_thread()): + cbook._warn_external( + "Starting a Matplotlib GUI outside of the main thread will likely " + "fail.") + + def new_figure_manager(*args, **kwargs): """Create a new figure manager instance.""" + _warn_if_gui_out_of_main_thread() return _backend_mod.new_figure_manager(*args, **kwargs) @@ -270,6 +287,7 @@ def show(*args, **kwargs): This is experimental, and may be set to ``True`` or ``False`` to override the blocking behavior described above. """ + _warn_if_gui_out_of_main_thread() return _backend_mod.show(*args, **kwargs) From ad90166f3a10db2950c181f72d66953d94a72d9a Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 13 Dec 2019 14:33:13 +0100 Subject: [PATCH 3/3] Some explanatory comments. --- lib/matplotlib/pyplot.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 0214d4ac1b9d..492b09f418b4 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -221,6 +221,11 @@ def switch_backend(newbackend): rcParamsOrig["backend"] = "agg" return + # Backends are implemented as modules, but "inherit" default method + # implementations from backend_bases._Backend. This is achieved by + # creating a "class" that inherits from backend_bases._Backend and whose + # body is filled with the module's globals. + backend_name = cbook._backend_module_name(newbackend) class backend_mod(matplotlib.backend_bases._Backend): @@ -258,16 +263,19 @@ def _warn_if_gui_out_of_main_thread(): "fail.") +# This function's signature is rewritten upon backend-load by switch_backend. def new_figure_manager(*args, **kwargs): """Create a new figure manager instance.""" _warn_if_gui_out_of_main_thread() return _backend_mod.new_figure_manager(*args, **kwargs) +# This function's signature is rewritten upon backend-load by switch_backend. def draw_if_interactive(*args, **kwargs): return _backend_mod.draw_if_interactive(*args, **kwargs) +# This function's signature is rewritten upon backend-load by switch_backend. def show(*args, **kwargs): """ Display all figures.