You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The print statements were added precisely because silent failures were happening before; i.e. you run something which is forcing a qt event loop hook without asking, and then you muck around with some tk stuff and nothing works.
Happy to help make this quieter, and maybe work on something that allows multiple event loop hooks to run in sequence, which I believe should be possible as well.
I think the problem is that Matplotlib sets the Qt backend by default in some cases, which generates this message in IPython when users try to set that backend again.
So, perhaps the message shouldn't be shown when the backend you're trying to set is the same one that you already have.
@forallsunday can you post relevant package versions?
I run IPython 8.12.0 with Qt5 on Gentoo. In my configuration I set c.TerminalIPythonApp.matplotlib = 'qt5'
otherwise I use default settings except for some prompt configs.
I run IPython 8.12.0 with Qt5 on Gentoo. In my configuration I set c.TerminalIPythonApp.matplotlib = 'qt5' otherwise I use default settings except for some prompt configs.
OK, I reproduced this with a config file containing only
c = get_config() #noqa
c.TerminalIPythonApp.matplotlib = 'qt5'
and a virtualenv containing only matplotlib, pyqt5, and ipython. I'll start working on this.
My intent is to make an effort to ignore multiple requests for the same GUI event loop, but keep all the messaging. If lots of people are against that, maybe we can add a --verbose flag or something similar to what I did for autoreload (which has -p or -l for showing messages via print or log).
OK, there's a bit of a mess here with Qt versions.
When IPython starts, the profile configuration is read. After the banner is printed, TerminalIPythonApp.init_gui_pylab() executes.
The user, in this case, has set matplotlib to qt5 in their config. So init_gui_pylab calls InteractiveShellApp.enable_matplotlib('qt5').
This, via a call to matplotlib.get_backend(), will eventually call matplotlib.pyplot.switch_backend(xxx) where xxx is the first in the list ["macosx", "qtagg", "gtk4agg", "gtk3agg", "tkagg", "wxagg"] to succeed.
Note that this call comes from trying to retrieve the value rcParams['backend']; effectively asking for a backend will set it.
So, for both Windows and Linux, this would be qtagg.
This causes matplotlib to import the latest Qt in the system.
At the very end of the call to matplotlib.pyplot.switch_backend('qtagg'), pyplot.install_repl_displayhook() is called, and this effectively calls %gui yyy, where yyy comes from the mappings below.
pyplot.install_repl_displayhook() goes into IPython.core.pylabtools to look for which GUI event hook goes with which backend. Currently, there is this mapping from GUI->backend:
I see a discrepancy here: If qt6 maps to QtAgg, why does qt map to Qt5Agg?
The reverse mapping from backend->GUI has modifications:
# There needs to be a hysteresis here as the new QtAgg Matplotlib backend
# supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5,
# and Qt6.
backend2gui["QtAgg"] = "qt"
backend2gui["Qt4Agg"] = "qt"
backend2gui["Qt5Agg"] = "qt"
I don't follow these. If there are specifically versioned backends, and the Qt imports on both matplotlib and IPython are specifically versioned, and we assume qt means latest, these shouldn't be here. (I can certainly remove any reference to Qt4 and older since support for it was removed in #13864.)
Note that this all happens before actually processing whatever the user set TerminalIPythonApp.matplotlib to in their config file. This definition of backend2gui will just return qt, so before even reading the user's config file, we will install an event loop hook for the latest installed version of Qt. For example, in a system with both pyside6 and pyqt5 installed, this will import pyside6 into the session and install a hook for it.
This entire sequence is being triggered by importing matplotlib_inline.backend_inline. In short, InteractiveShell.enable_matplotlib is first calling %gui qt and then immediately trying to call %gui yyy, where yyy is what the user asked for in their config. If the two Qt versions don't match, this will fail.
(This is more of a note to self; let me think about this more and see how we can proceed.)
There is a winding road to get here and I am not sure I have all of the details correct, but my rough understanding is:
IPython provides nicer names (without the agg) to select the backend
these names date back probably 15+ years and the current versions of Qt
in Matplotlib we had a pattern of doing a new backend for each GUI toolkit version (backend_qt4agg.py, backend_qt5agg.py, backend_gtk3agg.py, backend_gtk4agg.py) etc
when qt6 came out instead of making a backend_qt6agg.py we managed to push enough compatibility shims into backends/qt_compat.py that we could support all 4 Qt bindings (pyside / pyqt X Qt5, Qt6) and made one new backend called backend_qtagg.py. Support for PyQt6/PySide6. matplotlib/matplotlib#19255 . This is mpl3.5
we kept backend_qt5agg.py around as a very thin shim (and a knob to force qt_compat.py to pick one of the Qt5 bindings) because there is no good reason to break any users who are importing directly
It looks like I was the last person to touch the forward mapping of 'qt' in 127b45b when I changed 'qt' to map to qt5agg!
Thanks @tacaswell; I'll need some time to process your comment.
In the meantime, I've started a draft pull request. The messaging is improved (that's the easy part) but I left a comment there because importing matplotlib_inline.backend_inline triggers a GUI event loop hook that I don't think is needed, but I need more guidance on that.
but I left a comment there because importing matplotlib_inline.backend_inline triggers a GUI event loop hook that I don't think is needed, but I need more guidance on that.
I'm 75% sure that is something that needs to be resolved in the inline backend.
The other thing to check is that the default backend in Matplotlib in now "auto", however if you ask Matplotlib what the current backend is or create a figure we will sort out what the best currently available backend is. If you have any of the Qt bindings installed, we will pick that (as interactive figures are better than dead figures).
Without a backend explicitly set, Matplotlib automatically detects a usable backend based on what is available on your system and on whether a GUI event loop is already running. The first usable backend in the following list is selected: MacOSX, QtAgg, GTK4Agg, Gtk3Agg, TkAgg, WxAgg, Agg. The last, Agg, is a non-interactive backend that can only write to files. It is used on Linux, if Matplotlib cannot connect to either an X display or a Wayland display.
I do not see matplotlib_inline doing anything I expect to force the backend, but I may have missed it or am forgetting another condition that force us to resolve the backend.
The other thing to check is that the default backend in Matplotlib in now "auto"
This is exactly what matplotlib_inline is triggering, by calling _enable_matplotlib_integration() in backend_inline.py. That calls matplotlib.get_backend(), which will trigger __getitem__ in RcParams and execute this:
I just wrote up #14089; maybe we can take the choice away from users who don't really care? Especially with this Qt version stuff, I wonder if we support it just because the code is there and not because most people actually need it.
Activity
nodatapoints commentedon Apr 7, 2023
I think this is due to #13957.
The print statements seem hardcoded, which I don't like.
An option to disable these debug messages would be nice, since I do not need to be reminded of the event loop on every startup.
francescoboc commentedon Apr 21, 2023
I agree with @nodatapoints , I would like to suppress the message
Installed qt5 event loop hook..ccordoba12 commentedon Apr 28, 2023
@shaperilio, how do you think we should address this issue?
shaperilio commentedon Apr 28, 2023
Is there a global "debug" flag?
The print statements were added precisely because silent failures were happening before; i.e. you run something which is forcing a
qtevent loop hook without asking, and then you muck around with sometkstuff and nothing works.Happy to help make this quieter, and maybe work on something that allows multiple event loop hooks to run in sequence, which I believe should be possible as well.
ccordoba12 commentedon Apr 28, 2023
I think the problem is that Matplotlib sets the Qt backend by default in some cases, which generates this message in IPython when users try to set that backend again.
So, perhaps the message shouldn't be shown when the backend you're trying to set is the same one that you already have.
shaperilio commentedon Apr 28, 2023
This was certainly the intent. Let me take a look at what's going on.
@forallsunday can you post relevant package versions?
nodatapoints commentedon Apr 29, 2023
I run IPython 8.12.0 with Qt5 on Gentoo. In my configuration I set
c.TerminalIPythonApp.matplotlib = 'qt5'otherwise I use default settings except for some prompt configs.
shaperilio commentedon May 1, 2023
What is
c?ccordoba12 commentedon May 2, 2023
It's the IPython config object. See here for more details:
https://ipython.readthedocs.io/en/stable/config/intro.html
shaperilio commentedon May 2, 2023
OK, I reproduced this with a config file containing only
and a virtualenv containing only
matplotlib,pyqt5, andipython. I'll start working on this.My intent is to make an effort to ignore multiple requests for the same GUI event loop, but keep all the messaging. If lots of people are against that, maybe we can add a
--verboseflag or something similar to what I did forautoreload(which has-por-lfor showing messages viaprintorlog).ccordoba12 commentedon May 2, 2023
I think ignoring warnings for the same event loop is fine for now.
shaperilio commentedon May 2, 2023
OK, there's a bit of a mess here with Qt versions.
When IPython starts, the profile configuration is read. After the banner is printed,
TerminalIPythonApp.init_gui_pylab()executes.The user, in this case, has set
matplotlibtoqt5in their config. Soinit_gui_pylabcallsInteractiveShellApp.enable_matplotlib('qt5').This, via a call to
matplotlib.get_backend(), will eventually callmatplotlib.pyplot.switch_backend(xxx)wherexxxis the first in the list["macosx", "qtagg", "gtk4agg", "gtk3agg", "tkagg", "wxagg"]to succeed.Note that this call comes from trying to retrieve the value
rcParams['backend']; effectively asking for a backend will set it.So, for both Windows and Linux, this would be
qtagg.This causes
matplotlibto import the latest Qt in the system.At the very end of the call to
matplotlib.pyplot.switch_backend('qtagg'),pyplot.install_repl_displayhook()is called, and this effectively calls%gui yyy, whereyyycomes from the mappings below.pyplot.install_repl_displayhook()goes intoIPython.core.pylabtoolsto look for which GUI event hook goes with which backend. Currently, there is this mapping from GUI->backend:I see a discrepancy here: If
qt6maps toQtAgg, why doesqtmap toQt5Agg?The reverse mapping from backend->GUI has modifications:
I don't follow these. If there are specifically versioned backends, and the Qt imports on both
matplotlibandIPythonare specifically versioned, and we assumeqtmeans latest, these shouldn't be here. (I can certainly remove any reference to Qt4 and older since support for it was removed in #13864.)Note that this all happens before actually processing whatever the user set
TerminalIPythonApp.matplotlibto in their config file. This definition ofbackend2guiwill just returnqt, so before even reading the user's config file, we will install an event loop hook for the latest installed version of Qt. For example, in a system with bothpyside6andpyqt5installed, this will importpyside6into the session and install a hook for it.This entire sequence is being triggered by importing
matplotlib_inline.backend_inline. In short,InteractiveShell.enable_matplotlibis first calling%gui qtand then immediately trying to call%gui yyy, whereyyyis what the user asked for in their config. If the two Qt versions don't match, this will fail.(This is more of a note to self; let me think about this more and see how we can proceed.)
shaperilio commentedon May 2, 2023
@tacaswell do you have any thoughts on the backend<->GUI mapping?
tacaswell commentedon May 2, 2023
There is a winding road to get here and I am not sure I have all of the details correct, but my rough understanding is:
backend_qt6agg.pywe managed to push enough compatibility shims intobackends/qt_compat.pythat we could support all 4 Qt bindings (pyside / pyqt X Qt5, Qt6) and made one new backend calledbackend_qtagg.py. Support for PyQt6/PySide6. matplotlib/matplotlib#19255 . This is mpl3.5backend_qt5agg.pyaround as a very thin shim (and a knob to forceqt_compat.pyto pick one of the Qt5 bindings) because there is no good reason to break any users who are importing directly'qt'in 127b45b when I changed'qt'to map to qt5agg!%matplotlib qtselectsqtaggI think the course of action here should be:
qtto beqtaggI'll open this PR.
This is closely related to #14038
shaperilio commentedon May 2, 2023
Thanks @tacaswell; I'll need some time to process your comment.
In the meantime, I've started a draft pull request. The messaging is improved (that's the easy part) but I left a comment there because importing
matplotlib_inline.backend_inlinetriggers a GUI event loop hook that I don't think is needed, but I need more guidance on that.tacaswell commentedon May 2, 2023
I'm 75% sure that is something that needs to be resolved in the inline backend.
tacaswell commentedon May 2, 2023
The other thing to check is that the default backend in Matplotlib in now "auto", however if you ask Matplotlib what the current backend is or create a figure we will sort out what the best currently available backend is. If you have any of the Qt bindings installed, we will pick that (as interactive figures are better than dead figures).
https://matplotlib.org/stable/users/explain/backends.html#selecting-a-backend
I do not see
matplotlib_inlinedoing anything I expect to force the backend, but I may have missed it or am forgetting another condition that force us to resolve the backend.shaperilio commentedon May 3, 2023
This is exactly what
matplotlib_inlineis triggering, by calling_enable_matplotlib_integration()inbackend_inline.py. That callsmatplotlib.get_backend(), which will trigger__getitem__inRcParamsand execute this:https://github.com/matplotlib/matplotlib/blob/5189609b9d2c0ea0b78ceba9d4003358db160b81/lib/matplotlib/__init__.py#L750-L754
and that last line will go through the list mentioned in the docs.
So yes, it seems that
_enable_matplotlib_integration()should probably callmatplotlib._get_backend_or_none().shaperilio commentedon May 3, 2023
OK, #14067 implements what I think we should do.
I think it needs to wait to see what we hear from
matplotlib_inline.shaperilio commentedon May 30, 2023
I created a PR for this in
matplotlib_inline: ipython/matplotlib-inline#26Now
_enable_matplotlib_integration()won't trigger the auto-selection of a backend.shaperilio commentedon May 30, 2023
I just wrote up #14089; maybe we can take the choice away from users who don't really care? Especially with this Qt version stuff, I wonder if we support it just because the code is there and not because most people actually need it.
ENH: Don't show message when enable the current eventloop.
ENH: Don't show message when enable the current eventloop.
ENH: Don't show message when enable the current eventloop. (#14168)