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

Skip to content

Commit 77ba47c

Browse files
committed
backend switching.
See changes documented in the API changes file. Some followup cleanup (of the now unused old machinery) will come as a separate PR (left some "FIXME: Remove." comments). Changes to the build process (namely, getting rid of trying to detect the default backend in setupext.py) will come as a separate PR. I inlined pylab_setup into switch_backend (and deprecated the old version of pylab_setup) because otherwise the typical call stack would be `use()` -> `set rcParams['backend'] = ...` -> `switch_backend()` -> `pylab_setup()`, which is a bit of a mess; at least we can get rid of one of the layers.
1 parent 4fc9288 commit 77ba47c

File tree

10 files changed

+155
-95
lines changed

10 files changed

+155
-95
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
@@ -18,6 +18,7 @@ The following classes, methods, functions, and attributes are deprecated:
1818
- ``backend_ps.get_bbox``,
1919
- ``backend_qt5.error_msg_qt``, ``backend_qt5.exception_handler``,
2020
- ``backend_wx.FigureCanvasWx.macros``,
21+
- ``backends.pylab_setup``,
2122
- ``cbook.GetRealpathAndStat``, ``cbook.Locked``,
2223
- ``cbook.is_numlike`` (use ``isinstance(..., numbers.Number)`` instead),
2324
``cbook.listFiles``, ``cbook.unicode_safe``,

doc/api/next_api_changes/2018-06-27-AL.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
Changes to backend loading
22
``````````````````````````
33

4+
Assignment to ``rcParams["backend"]`` now sets the backend. Backends can now
5+
be switched until a figure is actually created.
6+
47
Failure to load backend modules (``macosx`` on non-framework builds and
58
``gtk3`` when running headless) now raises `ImportError` (instead of
69
`RuntimeError` and `TypeError`, respectively.

lib/matplotlib/__init__.py

Lines changed: 14 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,7 @@ def __exit__(self, exc_type, exc_value, exc_tb):
13171317
dict.update(rcParams, self._orig)
13181318

13191319

1320+
# FIXME: Remove.
13201321
_use_error_msg = """
13211322
This call to matplotlib.use() has no effect because the backend has already
13221323
been chosen; matplotlib.use() must be called *before* pylab, matplotlib.pyplot,
@@ -1329,62 +1330,23 @@ def __exit__(self, exc_type, exc_value, exc_tb):
13291330

13301331
def use(arg, warn=True, force=False):
13311332
"""
1332-
Set the matplotlib backend to one of the known backends.
1333+
Set the Matplotlib backend.
13331334
1334-
The argument is case-insensitive. *warn* specifies whether a
1335-
warning should be issued if a backend has already been set up.
1336-
*force* is an **experimental** flag that tells matplotlib to
1337-
attempt to initialize a new backend by reloading the backend
1338-
module.
1335+
The argument is case-insensitive. Switching to an interactive backend is
1336+
only safe if no event loop for another interactive backend has started.
1337+
Switching to and from non-interactive backends is safe.
13391338
1340-
.. note::
1341-
1342-
This function must be called *before* importing pyplot for
1343-
the first time; or, if you are not using pyplot, it must be called
1344-
before importing matplotlib.backends. If warn is True, a warning
1345-
is issued if you try and call this after pylab or pyplot have been
1346-
loaded. In certain black magic use cases, e.g.
1347-
:func:`pyplot.switch_backend`, we are doing the reloading necessary to
1348-
make the backend switch work (in some cases, e.g., pure image
1349-
backends) so one can set warn=False to suppress the warnings.
1350-
1351-
To find out which backend is currently set, see
1352-
:func:`matplotlib.get_backend`.
1339+
To find out which backend is currently set, see `matplotlib.get_backend`.
13531340
1341+
Parameters
1342+
----------
1343+
arg : str
1344+
The name of the backend to use.
13541345
"""
1355-
# Lets determine the proper backend name first
1356-
if arg.startswith('module://'):
1357-
name = arg
1358-
else:
1359-
# Lowercase only non-module backend names (modules are case-sensitive)
1360-
arg = arg.lower()
1361-
name = validate_backend(arg)
1362-
1363-
# Check if we've already set up a backend
1364-
if 'matplotlib.backends' in sys.modules:
1365-
# Warn only if called with a different name
1366-
if (rcParams['backend'] != name) and warn:
1367-
import matplotlib.backends
1368-
warnings.warn(
1369-
_use_error_msg.format(
1370-
backend=rcParams['backend'],
1371-
tb=matplotlib.backends._backend_loading_tb),
1372-
stacklevel=2)
1373-
1374-
# Unless we've been told to force it, just return
1375-
if not force:
1376-
return
1377-
need_reload = True
1378-
else:
1379-
need_reload = False
1380-
1381-
# Store the backend name
1382-
rcParams['backend'] = name
1383-
1384-
# If needed we reload here because a lot of setup code is triggered on
1385-
# module import. See backends/__init__.py for more detail.
1386-
if need_reload:
1387-
importlib.reload(sys.modules['matplotlib.backends'])
1346+
# We want to keep 'use(...); rcdefaults()' working, which means that
1347+
# use(...) needs to force the default backend too.
1348+
rcParams["backend"] = \
1349+
rcParamsDefault["backend"] = rcParamsOrig["backend"] = arg
13881350

13891351

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

lib/matplotlib/backend_bases.py

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

3213+
# Set to one of {"qt5", "qt4", "gtk3", "wx", "tk", "macosx"} if an
3214+
# interactive framework is required, or None otherwise.
3215+
required_interactive_framework = None
3216+
32133217
# `backend_version` may be overridden by the subclass.
32143218
backend_version = "unknown"
32153219

@@ -3292,7 +3296,8 @@ def show(cls, block=None):
32923296

32933297
@staticmethod
32943298
def export(cls):
3295-
for name in ["backend_version",
3299+
for name in ["required_interactive_framework",
3300+
"backend_version",
32963301
"FigureCanvas",
32973302
"FigureManager",
32983303
"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+
dict.__setitem__(rcParams, "backend", newbackend) # Don't recurse.
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):
@@ -2358,6 +2400,9 @@ def _autogen_docstring(base):
23582400
# to determine if they should trigger a draw.
23592401
install_repl_displayhook()
23602402

2403+
# Set up the backend.
2404+
switch_backend(rcParams["backend"])
2405+
23612406

23622407
################# REMAINING CONTENT GENERATED BY boilerplate.py ##############
23632408

lib/matplotlib/rcsetup.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
parameter set listed here should also be visited to the
1414
:file:`matplotlibrc.template` in matplotlib's root source directory.
1515
"""
16+
1617
from collections import Iterable, Mapping
1718
from functools import reduce
1819
import operator
1920
import os
2021
import re
22+
import sys
2123

2224
from matplotlib import cbook
2325
from matplotlib.cbook import ls_mapper
@@ -242,13 +244,17 @@ def validate_fonttype(s):
242244

243245
_validate_standard_backends = ValidateInStrings(
244246
'backend', all_backends, ignorecase=True)
247+
_auto_backend_sentinel = object()
245248

246249

247250
def validate_backend(s):
248-
if s.startswith('module://'):
249-
return s
250-
else:
251-
return _validate_standard_backends(s)
251+
backend = (
252+
s if s is _auto_backend_sentinel or s.startswith("module://")
253+
else _validate_standard_backends(s))
254+
pyplot = sys.modules.get("matplotlib.pyplot")
255+
if pyplot:
256+
pyplot.switch_backend(backend)
257+
return backend
252258

253259

254260
def validate_qt4(s):
@@ -965,9 +971,8 @@ def _validate_linestyle(ls):
965971

966972
# a map from key -> value, converter
967973
defaultParams = {
968-
'backend': ['Agg', validate_backend], # agg is certainly
969-
# present
970-
'backend_fallback': [True, validate_bool], # agg is certainly present
974+
'backend': [_auto_backend_sentinel, validate_backend],
975+
'backend_fallback': [True, validate_bool],
971976
'backend.qt4': [None, validate_qt4],
972977
'backend.qt5': [None, validate_qt5],
973978
'webagg.port': [8988, validate_int],

0 commit comments

Comments
 (0)