8000 ENH: backend switching. · matplotlib/matplotlib@56acb0f · GitHub
[go: up one dir, main page]

Skip to content

Commit 56acb0f

Browse files
anntzertacaswell
authored andcommitted
ENH: backend switching.
See changes documented in the API changes file. I inlined pylab_setup into switch_backend (and deprecated the old version of pylab_setup) because otherwise the typical call stack would be `use()` -> `switch_backend()` -> `pylab_setup()`, which is a bit of a mess; at least we can get rid of one of the layers.
1 parent c433932 commit 56acb0f

File tree

9 files changed

+169
-89
lines changed

9 files changed

+169
-89
lines changed

doc/api/next_api_changes/2018-02-15-AL-deprecations.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ The following classes, methods, functions, and attributes are deprecated:
2222
handle autorepeated key presses).
2323
- ``backend_qt5.error_msg_qt``, ``backend_qt5.exception_handler``,
2424
- ``backend_wx.FigureCanvasWx.macros``,
25+
- ``backends.pylab_setup``,
2526
- ``cbook.GetRealpathAndStat``, ``cbook.Locked``,
2627
- ``cbook.is_numlike`` (use ``isinstance(..., numbers.Number)`` instead),
2728
``cbook.listFiles``, ``cbook.unicode_safe``,

lib/matplotlib/__init__.py

Lines changed: 44 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,74 +1301,63 @@ def __exit__(self, exc_type, exc_value, exc_tb):
13011301
dict.update(rcParams, self._orig)
13021302

13031303

1304-
_use_error_msg = """
1305-
This call to matplotlib.use() has no effect because the backend has already
1306-
been chosen; matplotlib.use() must be called *before* pylab, matplotlib.pyplot,
1307-
or matplotlib.backends is imported for the first time.
1308-
1309-
The backend was *originally* set to {backend!r} by the following code:
1310-
{tb}
1311-
"""
1312-
1313-
13141304
def use(arg, warn=True, force=False):
13151305
"""
13161306
Set the matplotlib backend to one of the known backends.
13171307
1318-
The argument is case-insensitive. *warn* specifies whether a
1319-
warning should be issued if a backend has already been set up.
1320-
*force* is an **experimental** flag that tells matplotlib to
1321-
attempt to initialize a new backend by reloading the backend
1322-
module.
1308+
To find out which backend is currently set, see
1309+
:func:`matplotlib.get_backend`.
1310+
1311+
1312+
Parameters
1313+
----------
1314+
arg : str
1315+
The backend to switch to. This can either be one of the
1316+
'standard' backend names or a string of the form
1317+
``module://my.module.name``. This value is case-insensitive.
13231318
1324-
.. note::
1319+
warn : bool, optional
1320+
If True, warn if this is called after pyplot has been imported
1321+
and a backend is set up.
13251322
1326-
This function must be called *before* importing pyplot for
1327-
the first time; or, if you are not using pyplot, it must be called
1328-
before importing matplotlib.backends. If warn is True, a warning
1329-
is issued if you try and call this after pylab or pyplot have been
1330-
loaded. In certain black magic use cases, e.g.
1331-
:func:`pyplot.switch_backend`, we are doing the reloading necessary to
1332-
make the backend switch work (in some cases, e.g., pure image
1333-
backends) so one can set warn=False to suppress the warnings.
1323+
defaults to True
1324+
1325+
force : bool, optional
1326+
If True, attempt to switch the backend. This defaults to
1327+
false and using `.pyplot.switch_backend` is preferred.
13341328
1335-
To find out which backend is currently set, see
1336-
:func:`matplotlib.get_backend`.
13371329
13381330
"""
1339-
# Lets determine the proper backend name first
1340-
if arg.startswith('module://'):
1341-
name = arg
1342-
else:
1343-
# Lowercase only non-module backend names (modules are case-sensitive)
1344-
arg = arg.lower()
1345-
name = validate_backend(arg)
1346-
1347-
# Check if we've already set up a backend
1348-
if 'matplotlib.backends' in sys.modules:
1349-
# Warn only if called with a different name
1350-
if (rcParams['backend'] != name) and warn:
1351-
import matplotlib.backends
1331+
name = validate_backend(arg)
1332+
1333+
# if setting back to the same thing, do nothing
1334+
if (rcParams['backend'] == name):
1335+
pass
1336+
1337+
# Check if we have already imported pyplot and triggered
1338+
# backend selection, do a bit more work
1339+
elif 'matplotlib.pyplot' in sys.modules:
1340+
# If we are here then the requested is different than the current.
1341+
# If we are going to force the switch, never warn, else, if warn
1342+
# is True, then direct users to `plt.switch_backend`
1343+
if (not force) and warn:
13521344
warnings.warn(
1353-
_use_error_msg.format(
1354-
backend=rcParams['backend'],
1355-
tb=matplotlib.backends._backend_loading_tb),
1345+
("matplotlib.pyplot as already been imported, "
1346+
"this call will have no effect."),
13561347
stacklevel=2)
13571348

1358-
# Unless we've been told to force it, just return
1359-
if not force:
1360-
return
1361-
need_reload = True
1349+
# if we are going to force switching the backend, pull in
1350+
# `switch_backend` from pyplot. This will only happen if
1351+
# pyplot is already imported.
1352+
if force:
1353+
from matplotlib.pyplot import switch_backend
1354+
switch_backend(name)
1355+
# Finally if pyplot is not imported update both rcParams and
1356+
# rcDefaults so restoring the defaults later with rcdefaults
1357+
# won't change the backend. This is a bit of overkill as 'backend'
1358+
# is already in style.core.STYLE_BLACKLIST, but better to be safe.
13621359
else:
1363-
need_reload = False
1364-
1365-
# Store the backend name
1366-
rcParams['backend'] = name
1367-
1368-
# If needed we reload here because a lot of setup code is triggered on
1369-
# module import. See backends/__init__.py for more detail.
1370-
if need_reload:
1371-
importlib.reload(sys.modules['matplotlib.backends'])
1360+
rcParams['backend'] = rcParamsDefault['backend'] = name
13721361

13731362

13741363
if os.environ.get('MPLBACKEND'):

lib/matplotlib/backend_bases.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3183,6 +3183,10 @@ class _Backend(object):
31833183
# class FooBackend(_Backend):
31843184
# # override the attributes and methods documented below.
31853185

3186+
# Set to one of {"qt5", "qt4", "gtk3", "wx", "tk", "macosx"} if an
3187+
# interactive framework is required, or None otherwise.
3188+
required_interactive_framework = None
3189+
31863190
# `backend_version` may be overridden by the subclass.
31873191
backend_version = "unknown"
31883192

@@ -3265,7 +3269,8 @@ def show(cls, block=None):
32653269

32663270
@staticmethod
32673271
def export(cls):
3268-
for name in ["backend_version",
3272+
for name in ["required_interactive_framework",
3273+
"backend_version",
32693274
"FigureCanvas",
32703275
"FigureManager",
32713276
"new_figure_manager",

lib/matplotlib/backends/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import traceback
66

77
import matplotlib
8+
from matplotlib import cbook
89
from matplotlib.backend_bases import _Backend
910

1011
_log = logging.getLogger(__name__)
1112

1213
backend = matplotlib.get_backend()
14+
# FIXME: Remove.
1315
_backend_loading_tb = "".join(
1416
line for line in traceback.format_stack()
1517
# Filter out line noise from importlib line.
@@ -64,6 +66,7 @@ def _get_running_interactive_framework():
6466
return None
6567

6668

69+
@cbook.deprecated("3.0")
6770
def pylab_setup(name=None):
6871
"""
6972
Return new_figure_manager, draw_if_interactive and show for pyplot.

lib/matplotlib/pyplot.py

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
The object-oriented API is recommended for more complex plots.
1919
"""
2020

21+
import importlib
2122
import inspect
23+
import logging
2224
from numbers import Number
2325
import re
2426
import sys
@@ -29,7 +31,7 @@
2931
import matplotlib
3032
import matplotlib.colorbar
3133
import matplotlib.image
32-
from matplotlib import style
34+
from matplotlib import rcsetup, style
3335
from matplotlib import _pylab_helpers, interactive
3436
from matplotlib.cbook import (
3537
dedent, deprecated, silent_list, warn_deprecated, _string_to_bool)
@@ -67,10 +69,13 @@
6769
MaxNLocator
6870
from matplotlib.backends import pylab_setup
6971

72+
_log = logging.getLogger(__name__)
73+
7074

7175
## Backend detection ##
7276

7377

78+
# FIXME: Deprecate.
7479
def _backend_selection():
7580
"""
7681
If rcParams['backend_fallback'] is true, check to see if the
@@ -110,8 +115,6 @@ def _backend_selection():
110115
## Global ##
111116

112117

113-
_backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup()
114-
115118
_IP_REGISTERED = None
116119
_INSTALL_FIG_OBSERVER = False
117120

@@ -213,21 +216,60 @@ def findobj(o=None, match=None, include_self=True):
213216

214217
def switch_backend(newbackend):
215218
"""
216-
Switch the default backend. This feature is **experimental**, and
217-
is only expected to work switching to an image backend. e.g., if
218-
you have a bunch of PostScript scripts that you want to run from
219-
an interactive ipython session, you may want to switch to the PS
220-
backend before running them to avoid having a bunch of GUI windows
221-
popup. If you try to interactively switch from one GUI backend to
222-
another, you will explode.
219+
Close all open figures and set the Matplotlib backend.
223220
224-
Calling this command will close all open windows.
221+
The argument is case-insensitive. Switching to an interactive backend is
222+
possible only if no event loop for another interactive backend has started.
223+
Switching to and from non-interactive backends is always possible.
224+
225+
Parameters
226+
----------
227+
newbackend : str
228+
The name of the backend to use.
225229
"""
226-
close('all')
230+
close("all")
231+
232+
if newbackend is rcsetup._auto_backend_sentinel:
233+
for candidate in ["macosx", "qt5agg", "qt4agg", "gtk3agg", "gtk3cairo",
234+
"tkagg", "wxagg", "agg", "cairo"]:
235+
try:
236+
switch_backend(candidate)
237+
except ImportError:
238+
continue
239+
else:
240+
return
241+
242+
backend_name = (
243+
newbackend[9:] if newbackend.startswith("module://")
244+
else "matplotlib.backends.backend_{}".format(newbackend.lower()))
245+
246+
backend_mod = importlib.import_module(backend_name)
247+
Backend = type(
248+
"Backend", (matplotlib.backends._Backend,), vars(backend_mod))
249+
_log.info("Loaded backend %s version %s.",
250+
newbackend, Backend.backend_version)
251+
252+
required_framework = Backend.required_interactive_framework
253+
current_framework = \
254+
matplotlib.backends._get_running_interactive_framework()
255+
if (current_framework and required_framework
256+
and current_framework != required_framework):
257+
raise ImportError(
258+
"Cannot load backend {!r} which requires the {!r} interactive "
259+
"framework, as {!r} is currently running".format(
260+
newbackend, required_framework, current_framework))
261+
262+
rcParams['backend'] = rcParamsDefault['backend'] = newbackend
263+
227264
global _backend_mod, new_figure_manager, draw_if_interactive, _show
228-
matplotlib.use(newbackend, warn=False, force=True)
229-
from matplotlib.backends import pylab_setup
230-
_backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup()
265+
_backend_mod = backend_mod
266+
new_figure_manager = Backend.new_figure_manager
267+
draw_if_interactive = Backend.draw_if_interactive
268+
_show = Backend.show
269+
270+
# Need to keep a global reference to the backend for compatibility reasons.
271+
# See https://github.com/matplotlib/matplotlib/issues/6092
272+
matplotlib.backends.backend = newbackend
231273

232274

233275
def show(*args, **kw):
@@ -2364,6 +2406,9 @@ def _autogen_docstring(base):
23642406
# to determine if they should trigger a draw.
23652407
install_repl_displayhook()
23662408

2409+
# Set up the backend.
2410+
switch_backend(rcParams["backend"])
2411+
23672412

23682413
################# REMAINING CONTENT GENERATED BY boilerplate.py ##############
23692414

lib/matplotlib/rcsetup.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import operator
1919
import os
2020
import re
21+
import sys
2122

2223
from matplotlib import cbook
2324
from matplotlib.cbook import ls_mapper
@@ -242,13 +243,14 @@ def validate_fonttype(s):
242243

243244
_validate_standard_backends = ValidateInStrings(
244245
'backend', all_backends, ignorecase=True)
246+
_auto_backend_sentinel = object()
245247

246248

247249
def validate_backend(s):
248-
if s.startswith('module://'):
249-
return s
250-
else:
251-
return _validate_standard_backends(s)
250+
backend = (
251+
s if s is _auto_backend_sentinel or s.startswith("module://")
252+
else _validate_standard_backends(s))
253+
return backend
252254

253255

254256
def validate_qt4(s):
@@ -965,9 +967,8 @@ def _validate_linestyle(ls):
965967

966968
# a map from key -> value, converter
967969
defaultParams = {
968-
'backend': ['Agg', validate_backend], # agg is certainly
969-
# present
970-
'backend_fallback': [True, validate_bool], # agg is certainly present
970+
'backend': [_auto_backend_sentinel, validate_backend],
971+
'backend_fallback': [True, validate_bool],
971972
'backend.qt4': [None, validate_qt4],
972973
'backend.qt5': [None, validate_qt5],
973974
'webagg.port': [8988, validate_int],

0 commit comments

Comments
 (0)
0