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

Skip to content

Commit 208d5e3

Browse files
committed
Always import pyplot when calling matplotlib.use().
This avoids having to reason about various delayed backend resolution strategies.
1 parent df2a26d commit 208d5e3

File tree

3 files changed

+64
-33
lines changed

3 files changed

+64
-33
lines changed

lib/matplotlib/__init__.py

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,44 +1259,39 @@ def use(backend, warn=False, force=True):
12591259
or a string of the form: ``module://my.module.name``.
12601260
12611261
warn : bool, optional, default: False
1262-
If True and not *force*, warn that the call will have no effect if
1263-
this is called after pyplot has been imported and a backend is set up.
1262+
If True and not *force*, emit a warning if a failure-to-switch
1263+
`ImportError` has been suppressed.
12641264
12651265
force : bool, optional, default: True
1266-
If True, attempt to switch the backend. An ImportError is raised if
1267-
an interactive backend is selected, but another interactive
1268-
backend has already started.
1266+
If True (the default), raise an `ImportError` if the backend cannot be
1267+
set up (either because it fails to import, or because an incompatible
1268+
GUI interactive framework is already running); if False, ignore the
1269+
failure.
12691270
12701271
See Also
12711272
--------
12721273
:ref:`backends`
12731274
matplotlib.get_backend
12741275
"""
12751276
name = validate_backend(backend)
1276-
12771277
if dict.__getitem__(rcParams, 'backend') == name:
12781278
# Nothing to do if the requested backend is already set
12791279
pass
1280-
elif 'matplotlib.pyplot' in sys.modules:
1281-
# pyplot has already been imported (which triggered backend selection)
1282-
# and the requested backend is different from the current one.
1283-
if force:
1284-
# if we are going to force switching the backend, pull in
1285-
# `switch_backend` from pyplot (which is already imported).
1286-
from matplotlib.pyplot import switch_backend
1287-
switch_backend(name)
1288-
elif warn:
1289-
# Only if we are not going to force the switch *and* warn is True,
1290-
# then direct users to `plt.switch_backend`.
1291-
cbook._warn_external(
1292-
"matplotlib.pyplot has already been imported, "
1293-
"this call will have no effect.")
12941280
else:
1295-
# Finally if pyplot is not imported update both rcParams and
1296-
# rcDefaults so restoring the defaults later with rcdefaults
1297-
# won't change the backend. This is a bit of overkill as 'backend'
1298-
# is already in style.core.STYLE_BLACKLIST, but better to be safe.
1281+
# Update both rcParams and rcDefaults so restoring the defaults later
1282+
# with rcdefaults won't change the backend. This is a bit of overkill
1283+
# as 'backend' is already in style.core.STYLE_BLACKLIST, but better to
1284+
# be safe.
12991285
rcParams['backend'] = rcParamsDefault['backend'] = name
1286+
try:
1287+
from matplotlib import pyplot as plt
1288+
plt.switch_backend(name)
1289+
except ImportError as exc:
1290+
if force:
1291+
raise
1292+
if warn:
1293+
cbook._warn_external(
1294+
f"Failed to switch backend to {backend}: {exc}")
13001295

13011296

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

lib/matplotlib/tests/test_rcparams.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from collections import OrderedDict
22
import copy
33
import os
4+
from pathlib import Path
5+
import subprocess
6+
import sys
47
from unittest import mock
58

69
from cycler import cycler, Cycler
@@ -479,3 +482,28 @@ def test_if_rctemplate_would_be_valid(tmpdir):
479482
fail_on_error=True,
480483
use_default_template=False)
481484
assert len(record) == 0
485+
486+
487+
@pytest.mark.skipif(sys.platform != "linux", reason="Linux only")
488+
def test_backend_fallback_headless(tmpdir):
489+
env = {**os.environ,
490+
"DISPLAY": "", "MPLBACKEND": "", "MPLCONFIGDIR": str(tmpdir)}
491+
with pytest.raises(subprocess.CalledProcessError):
492+
subprocess.run(
493+
[sys.executable, "-c",
494+
"import matplotlib; matplotlib.use('tkagg')"],
495+
env=env, check=True)
496+
497+
498+
@pytest.mark.skipif(sys.platform == "linux" and not os.environ.get("DISPLAY"),
499+
reason="headless")
500+
def test_backend_fallback_headful(tmpdir):
501+
pytest.importorskip("tkinter")
502+
env = {**os.environ, "MPLBACKEND": "", "MPLCONFIGDIR": str(tmpdir)}
503+
backend = subprocess.check_output(
504+
[sys.executable, "-c",
505+
"import matplotlib.pyplot; print(matplotlib.get_backend())"],
506+
env=env, universal_newlines=True)
507+
# The actual backend will depend on what's installed, but at least tkagg is
508+
# present.
509+
assert backend.strip().lower() != "agg"

tutorials/introductory/usage.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -316,15 +316,22 @@ def my_plotter(ax, data1, data2, param_dict):
316316
# "interactive backends") and hardcopy backends to make image files
317317
# (PNG, SVG, PDF, PS; also referred to as "non-interactive backends").
318318
#
319-
# There are four ways to configure your backend. If they conflict each other,
319+
# There are three ways to configure your backend. If they conflict each other,
320320
# the method mentioned last in the following list will be used, e.g. calling
321321
# :func:`~matplotlib.use()` will override the setting in your ``matplotlibrc``.
322322
#
323-
#
324-
# #. The ``backend`` parameter in your ``matplotlibrc`` file (see
323+
# #. The :rc:`backend` parameter in your ``matplotlibrc`` file (see
325324
# :doc:`/tutorials/introductory/customizing`)::
326325
#
327-
# backend : WXAgg # use wxpython with antigrain (agg) rendering
326+
# backend : qt5agg # use pyqt5 with antigrain (agg) rendering
327+
#
328+
# If no backend is explicitly set in the ``matplotlibrc`` file, Matplotlib
329+
# automatically detects a usable backend based on what is available on your
330+
# system and on whether a GUI event loop is already running.
331+
#
332+
# On Linux, if the environment variable :envvar:`DISPLAY` is unset, the
333+
# "event loop" is identified as "headless", which causes a fallback to a
334+
# noninteractive backend (agg).
328335
#
329336
# #. Setting the :envvar:`MPLBACKEND` environment variable, either for your
330337
# current shell or for a single script. On Unix::
@@ -351,12 +358,13 @@ def my_plotter(ax, data1, data2, param_dict):
351358
# import matplotlib
352359
# matplotlib.use('PS') # generate postscript output by default
353360
#
354-
# If you use the :func:`~matplotlib.use` function, this must be done before
355-
# importing :mod:`matplotlib.pyplot`. Calling :func:`~matplotlib.use` after
356-
# pyplot has been imported will have no effect. Using
357-
# :func:`~matplotlib.use` will require changes in your code if users want to
361+
# If you use the `~matplotlib.use` function, this should be done before
362+
# importing :mod:`matplotlib.pyplot`. Calling `~matplotlib.use` after pyplot
363+
# has been imported may fail to switch the backend and raise an ImportError.
364+
#
365+
# Using `~matplotlib.use` will require changes in your code if users want to
358366
# use a different backend. Therefore, you should avoid explicitly calling
359-
# :func:`~matplotlib.use` unless absolutely necessary.
367+
# `~matplotlib.use` unless absolutely necessary.
360368
#
361369
# .. note::
362370
# Backend name specifications are not case-sensitive; e.g., 'GTK3Agg'

0 commit comments

Comments
 (0)