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

Skip to content

Commit 40583b0

Browse files
authored
Merge pull request #13977 from anntzer/use-force-pyplot
API: Always import pyplot when calling matplotlib.use().
2 parents 8b05b35 + 208d5e3 commit 40583b0

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
@@ -1248,44 +1248,39 @@ def use(backend, warn=False, force=True):
12481248
or a string of the form: ``module://my.module.name``.
12491249
12501250
warn : bool, optional, default: False
1251-
If True and not *force*, warn that the call will have no effect if
1252-
this is called after pyplot has been imported and a backend is set up.
1251+
If True and not *force*, emit a warning if a failure-to-switch
1252+
`ImportError` has been suppressed.
12531253
12541254
force : bool, optional, default: True
1255-
If True, attempt to switch the backend. An ImportError is raised if
1256-
an interactive backend is selected, but another interactive
1257-
backend has already started.
1255+
If True (the default), raise an `ImportError` if the backend cannot be
1256+
set up (either because it fails to import, or because an incompatible
1257+
GUI interactive framework is already running); if False, ignore the
1258+
failure.
12581259
12591260
See Also
12601261
--------
12611262
:ref:`backends`
12621263
matplotlib.get_backend
12631264
"""
12641265
name = validate_backend(backend)
1265-
12661266
if dict.__getitem__(rcParams, 'backend') == name:
12671267
# Nothing to do if the requested backend is already set
12681268
pass
1269-
elif 'matplotlib.pyplot' in sys.modules:
1270-
# pyplot has already been imported (which triggered backend selection)
1271-
# and the requested backend is different from the current one.
1272-
if force:
1273-
# if we are going to force switching the backend, pull in
1274-
# `switch_backend` from pyplot (which is already imported).
1275-
from matplotlib.pyplot import switch_backend
1276-
switch_backend(name)
1277-
elif warn:
1278-
# Only if we are not going to force the switch *and* warn is True,
1279-
# then direct users to `plt.switch_backend`.
1280-
cbook._warn_external(
1281-
"matplotlib.pyplot has already been imported, "
1282-
"this call will have no effect.")
12831269
else:
1284-
# Finally if pyplot is not imported update both rcParams and
1285-
# rcDefaults so restoring the defaults later with rcdefaults
1286-
# won't change the backend. This is a bit of overkill as 'backend'
1287-
# is already in style.core.STYLE_BLACKLIST, but better to be safe.
1270+
# Update both rcParams and rcDefaults so restoring the defaults later
1271+
# with rcdefaults won't change the backend. This is a bit of overkill
1272+
# as 'backend' is already in style.core.STYLE_BLACKLIST, but better to
1273+
# be safe.
12881274
rcParams['backend'] = rcParamsDefault['backend'] = name
1275+
try:
1276+
from matplotlib import pyplot as plt
1277+
plt.switch_backend(name)
1278+
except ImportError as exc:
1279+
if force:
1280+
raise
1281+
if warn:
1282+
cbook._warn_external(
1283+
f"Failed to switch backend to {backend}: {exc}")
12891284

12901285

12911286
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)