The difference between switch_backend and use is not quite clear.
Proposed fix
Consider renaming matplotlib.use (and either discourace or long-term deprecate use (with a pending deprecation)).
Check whether use and switch_backend can be unified. If not, at least improve the documentation.
Side note: The force parameter in use can be dropped.
matplotlib.use(..., force=True)
is equivalent to
try:
matplotlib.use(...)
except ImportError:
pass
It should be rare that users want to ignore that changing the backend fails. And if so, they can do it explicitly. We do not need to provide API for that. The only slight complication is that this the default behavior is force=True; i.e. dropping would change the default behavor. But we can address that in the course of "renaming": We simply provide a new function, e.g. set_backend with the new behavior.
use() is a somewhat obscure name, but it's so widely used that it's probably never removable.
The notion of having "a" backend is really a pyplot one (because that's basically what plt.figure()/plt.show() dispatch to), so in theory the backend-control functions should really be in pyplot (and in practice the actual logic, i.e. switch_backend, is indeed implemented in pyplot), but for a long time backends were not switchable after pyplot had been imported hence the existence of use() at the toplevel, which could be called (just to register whatever backend the user actually wants) before pyplot gets imported.
Now the situation is a bit better, and backend switches are possible, but still not perfect (just importing pyplot still tries to import the "current" backend and thus may load PyQt/wxPython/whatnot in the module space, which was e.g. a problem back when we supported PyQt4 and PyQt5 because these two modules cannot be imported in the same process at all, even without starting an event loop; I'd guess ideally we'd only import the backend module when actually calling plt.figure(), i.e. at the last possible moment). So it's still useful to have a backend setting function accessible before importing pyplot.
To summarize: I think we need to keep an API outside of pyplot (use) unless we further cleanup pyplot to delay even more the actual backend importing (which is probably doable and worthwhile); also that API may be very hard to ever deprecate. I also think it makes sense for the implementation to live in pyplot (because it's fundamentally a pyplot concept), though it could possibly be made private (I'm not sure I'd really want that, though).
One should also check how this plays with mpl-gui.
just importing pyplot still tries to import the "current" backend and thus may load
This isn't true in 3.7 any more:
✔ 17:05:51 [belanna] @ ipython
Python 3.11.3 (main, Jun 5 2023, 09:32:32) [GCC 13.1.1 20230429]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.14.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import matplotlib
In [2]: import matplotlib.pyplot as plt
In [3]: type(matplotlib.rcParams)
Out[3]: matplotlib.RcParams
In [4]: type(matplotlib.rcParams).mro()
Out[4]:
[matplotlib.RcParams,
collections.abc.MutableMapping,
collections.abc.Mapping,
collections.abc.Collection,
collections.abc.Sized,
collections.abc.Iterable,
collections.abc.Container,
dict,
object]
In [5]: dict.__getitem__(matplotlib.rcParams, 'backend')
Out[5]: <object at 0x7ff7fd8ffc80>
In [6]: matplotlib.__version__
Out[6]: '3.7.2'
In [7]:
but if you do anything that iterates over the rcParams (like tab-complete in IPython (🤯)) it will run though the fall-back logic and pick a backend. In IPython picking the backend will also aggressively start the event loop by installing the inputhook.
I agree with @anntzer "active backend" is really a pyplot concept and if possible we should push it as far into pyplot as we can.
The real trap we are in is:
users can use rcParams to set the backend
we want the rcParams['backend'] to always reflect reality
we do not want to return the auto-sentinel to users (and we need the auto sentinal to detect "the user is on a headless system and does not care, give them agg" vs "the user asked for Qt, they are on a headless system, raise because we can not do what they asked).
Giving up any of those three would make the problem a lot simpler. The softest one is probably the third (breaking type stability on rcParams['backend']) and letting the user know "we have not locked a backend yet". If we accept that we can drop the "maybe import pyplot" logic in the rcParams object.
I think the path would be then to:
stop documenting mpl.use and mpl.get_backend() in examples etc (agree with @anntzer that they are too widespread to actually deprecate)
highlight plt.switch_backend in our docs as the "right" way to force a backend of the user's choice
move the actual selection to only automatically happen when plt.figure is called (now it is that or when rcParams['backend'] or mpl.get_backend() is used).
The softest one is probably the third (breaking type stability on rcParams['backend']) and letting the user know "we have not locked a backend yet"
By the way when I first implemented the autoselection logic there was also the idea of allowing the user to explicitly set rcParams["backend"] to a list of strings (the list of backends that will be tested when we actually need to select a backend) instead of introducing an opaque object representing the (fixed) builtin fallback order (see discussion in the old threads). The idea was rejected on grounds of type stability, but may be perhaps worth revisiting (... or not). (Reading rcParams["backend"] could also return that list if we haven't selected the backend yet, or (as we do now) force selection and return just the selected backend.)
It would also be a lot easier if rcParams["backend"] would not need to reflect the current state, i.e. it's just a (list of) value(s) to look up when needed. Similar to prop_cycle not holding the actual cycler state.
But I don't see a reasonable migration strategy (other than deprecating rcParams["backend"] and replacing it with rcParams["default_backend"] and deferring all actual setting/retrieving actions to pyplot functions).
Do the name proposals mean, you consider it worthwile redesigning the rcParams backend interaction (including the implied API changes)?
I think it is worth considering so we can get away from rcParams having to be updated to match the selected backend.
I feel that the few things that are not "style" would ideally be segregated from rcParams.
Yes, but the ability to set the preferred backend via configuration file is too useful to give up and I think the cost of having backend information in rcParams is a lesser evil than having a second configuration file.
(Default t.b.d. resolve=True is what we are currently using, but resolve=False is the more sane default - triggering resolution as a side-effect should be opt-in)
I would think that most end-users who use this are doing some kind of debugging and want to know "which backend will mpl use?". So they would want resolve=True and be confused to get None by default.
Activity
anntzer commented on Aug 7, 2023
use() is a somewhat obscure name, but it's so widely used that it's probably never removable.
The notion of having "a" backend is really a pyplot one (because that's basically what plt.figure()/plt.show() dispatch to), so in theory the backend-control functions should really be in pyplot (and in practice the actual logic, i.e. switch_backend, is indeed implemented in pyplot), but for a long time backends were not switchable after pyplot had been imported hence the existence of use() at the toplevel, which could be called (just to register whatever backend the user actually wants) before pyplot gets imported.
Now the situation is a bit better, and backend switches are possible, but still not perfect (just importing pyplot still tries to import the "current" backend and thus may load PyQt/wxPython/whatnot in the module space, which was e.g. a problem back when we supported PyQt4 and PyQt5 because these two modules cannot be imported in the same process at all, even without starting an event loop; I'd guess ideally we'd only import the backend module when actually calling plt.figure(), i.e. at the last possible moment). So it's still useful to have a backend setting function accessible before importing pyplot.
To summarize: I think we need to keep an API outside of pyplot (use) unless we further cleanup pyplot to delay even more the actual backend importing (which is probably doable and worthwhile); also that API may be very hard to ever deprecate. I also think it makes sense for the implementation to live in pyplot (because it's fundamentally a pyplot concept), though it could possibly be made private (I'm not sure I'd really want that, though).
One should also check how this plays with mpl-gui.
tacaswell commented on Aug 7, 2023
This isn't true in 3.7 any more:
but if you do anything that iterates over the rcParams (like tab-complete in IPython (🤯)) it will run though the fall-back logic and pick a backend. In IPython picking the backend will also aggressively start the event loop by installing the inputhook.
I agree with @anntzer "active backend" is really a pyplot concept and if possible we should push it as far into pyplot as we can.
The real trap we are in is:
Giving up any of those three would make the problem a lot simpler. The softest one is probably the third (breaking type stability on rcParams['backend']) and letting the user know "we have not locked a backend yet". If we accept that we can drop the "maybe import pyplot" logic in the rcParams object.
I think the path would be then to:
anntzer commented on Aug 7, 2023
By the way when I first implemented the autoselection logic there was also the idea of allowing the user to explicitly set rcParams["backend"] to a list of strings (the list of backends that will be tested when we actually need to select a backend) instead of introducing an opaque object representing the (fixed) builtin fallback order (see discussion in the old threads). The idea was rejected on grounds of type stability, but may be perhaps worth revisiting (... or not). (Reading rcParams["backend"] could also return that list if we haven't selected the backend yet, or (as we do now) force selection and return just the selected backend.)
tacaswell commented on Aug 7, 2023
I am coming around the the list version more than when it was first proposed. plt.switch_backend([list, of, options]) also seems nice?
timhoffm commented on Aug 7, 2023
All of the above makes sense.
It would also be a lot easier if rcParams["backend"] would not need to reflect the current state, i.e. it's just a (list of) value(s) to look up when needed. Similar to prop_cycle not holding the actual cycler state.
But I don't see a reasonable migration strategy (other than deprecating rcParams["backend"] and replacing it with rcParams["default_backend"] and deferring all actual setting/retrieving actions to pyplot functions).
tacaswell commented on Aug 9, 2023
'proposed_backend' 'preferred_backend', 'backend_priority', 'backend_order'?
timhoffm commented on Aug 10, 2023
Do the name proposals mean, you consider it worthwile redesigning the rcParams backend interaction (including the implied API changes)?
jklymak commented on Aug 10, 2023
I feel that the few things that are not "style" would ideally be segregated from rcParams.
tacaswell commented on Aug 10, 2023
I think it is worth considering so we can get away from rcParams having to be updated to match the selected backend.
Yes, but the ability to set the preferred backend via configuration file is too useful to give up and I think the cost of having backend information in rcParams is a lesser evil than having a second configuration file.
5 remaining items
MNT: Add provisional get_backend(resolve=False) flag
MNT: Add provisional get_backend(resolve=False) flag
MNT: Add provisional get_backend(resolve=False) flag
MNT: Add provisional get_backend(resolve=False) flag
MNT: Add provisional get_backend(resolve=False) flag
MNT: Add provisional get_backend(resolve=False) flag
MNT: Add provisional get_backend(resolve=False) flag
MNT: Add provisional get_backend(resolve=False) flag
MNT: Add provisional get_backend(resolve=False) flag
MNT: Add provisional get_backend(resolve=False) flag
MNT: Add provisional get_backend(resolve=False) flag
rcomer commented on Oct 30, 2024
I would think that most end-users who use this are doing some kind of debugging and want to know "which backend will mpl use?". So they would want resolve=True and be confused to get None by default.
MNT: Deprecate rcParams._get("backend")
MNT: Deprecate rcParams._get("backend")
MNT: Deprecate rcParams._get("backend")