Skip to content

Backend switching #9795

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ ehthumbs.db
Icon?
Thumbs.db

# Things specific to this project #
###################################
lib/matplotlib/mpl-data/matplotlib.conf
lib/matplotlib/mpl-data/matplotlibrc

# Documentation generated files #
#################################
# sphinx build directory
Expand Down
23 changes: 23 additions & 0 deletions doc/api/api_changes/20171025-AL-rcParams-backend.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Testing multiple candidate backends in rcParams
```````````````````````````````````````````````

It is now possible to set ``rcParams["backend"]`` to a *list* of candidate
backends.

If `.pyplot` has already been imported, Matplotlib will try to load each
candidate backend in the given order until one of them can be loaded
successfully. ``rcParams["backend"]`` will then be set to the value of the
successfully loaded backend. (If `.pyplot` has already been imported and
``rcParams["backend"]`` is set to a single value, then the backend will
likewise be updated.)

If `.pyplot` has not been imported yet, then ``rcParams["backend"]`` will
maintain the value as a list, and the loading attempt will occur when `.pyplot`
is imported. If you rely on ``rcParams["backend"]`` (or its synonym,
``matplotlib.get_backend()`` always being a string, import `.pyplot` to trigger
backend resolution.

`matplotlib.use`, `pyplot.switch_backends`, and
`matplotlib.backends.pylab_setup` have likewise gained the ability to accept a
list of candidate backends. Note, however, that the first two functions have
become redundant with directly setting ``rcParams["backend"]``.
90 changes: 19 additions & 71 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@

import numpy
from six.moves.urllib.request import urlopen
from six.moves import reload_module as reload

# Get the version from the _version.py versioneer file. For a git checkout,
# this is computed based on the number of commits since the last tag.
Expand Down Expand Up @@ -1134,6 +1133,8 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True):

# this is the instance used by the matplotlib classes
rcParams = rc_params()
if "MPLBACKEND" in os.environ:
rcParams["backend"] = os.environ["MPLBACKEND"]

if rcParams['examples.directory']:
# paths that are intended to be relative to matplotlib_fname()
Expand Down Expand Up @@ -1311,80 +1312,28 @@ def rc_context(rc=None, fname=None):
dict.update(rcParams, orig)


_use_error_msg = """
This call to matplotlib.use() has no effect because the backend has already
been chosen; matplotlib.use() must be called *before* pylab, matplotlib.pyplot,
or matplotlib.backends is imported for the first time.

The backend was *originally* set to {backend!r} by the following code:
{tb}
"""


def use(arg, warn=True, force=False):
"""
Set the matplotlib backend to one of the known backends.
Set the Matplotlib backend.

The argument is case-insensitive. *warn* specifies whether a
warning should be issued if a backend has already been set up.
*force* is an **experimental** flag that tells matplotlib to
attempt to initialize a new backend by reloading the backend
module.
The argument is case-insensitive. Switching to an interactive backend is
only safe if no event loop for another interactive backend has started.
Switching to and from non-interactive backends is safe.

.. note::

This function must be called *before* importing pyplot for
the first time; or, if you are not using pyplot, it must be called
before importing matplotlib.backends. If warn is True, a warning
is issued if you try and call this after pylab or pyplot have been
loaded. In certain black magic use cases, e.g.
:func:`pyplot.switch_backend`, we are doing the reloading necessary to
make the backend switch work (in some cases, e.g., pure image
backends) so one can set warn=False to suppress the warnings.

To find out which backend is currently set, see
:func:`matplotlib.get_backend`.
To find out which backend is currently set, see `matplotlib.get_backend`.

Parameters
----------
arg : str
The name of the backend to use.
"""
# Lets determine the proper backend name first
if arg.startswith('module://'):
name = arg
else:
# Lowercase only non-module backend names (modules are case-sensitive)
arg = arg.lower()
name = validate_backend(arg)

# Check if we've already set up a backend
if 'matplotlib.backends' in sys.modules:
# Warn only if called with a different name
if (rcParams['backend'] != name) and warn:
import matplotlib.backends
warnings.warn(
_use_error_msg.format(
backend=rcParams['backend'],
tb=matplotlib.backends._backend_loading_tb),
stacklevel=2)

# Unless we've been told to force it, just return
if not force:
return
need_reload = True
else:
need_reload = False

# Store the backend name
rcParams['backend'] = name

# If needed we reload here because a lot of setup code is triggered on
# module import. See backends/__init__.py for more detail.
if need_reload:
reload(sys.modules['matplotlib.backends'])


try:
use(os.environ['MPLBACKEND'])
except KeyError:
pass
if not isinstance(arg, six.string_types):
# We want to keep 'use(...); rcdefaults()' working, which means that
# use(...) needs to force the default backend, and thus be a single
# string.
raise TypeError("matplotlib.use takes a single string as argument")
rcParams["backend"] = \
rcParamsDefault["backend"] = rcParamsOrig["backend"] = arg


def get_backend():
Expand Down Expand Up @@ -1508,8 +1457,7 @@ def test(verbosity=None, coverage=False, switch_backend_warn=True,

retcode = pytest.main(args, **kwargs)
finally:
if old_backend.lower() != 'agg':
use(old_backend, warn=switch_backend_warn)
rcParams['backend'] = old_backend
if recursionlimit:
sys.setrecursionlimit(old_recursionlimit)

Expand Down
Loading