TLDR; Use %matplotlib
if you want interactive plotting with matplotlib. If you’re only interested in the GUI’s event loop, %gui <backend>
is sufficient.
I never really understood the difference between %gui
and %matplotlib
in IPython. One of my colleagues at Enthought once told me that at some point in his career, he more or less stopped reading documentation and instead went straight to the code. That’s what I did here. But let’s do a bit of history first.
In the “beginning”, there was pylab
. It (still) is a module of matplotlib and was a flag to IPython designed to facilitate the adoption of Python as a numerical computing language by providing a MATLAB-like syntax.1 The reference was so explicit that before being renamed to pylab
on Dec 9, 2004, the module was called matplotlib.matlab
. IPython adopted the rename on the same day.2 With the ‑‑pylab
flag or the %pylab
magic function, IPython would set up matplotlib for interactive plotting and executed a number of imports from IPython, NumPy and matplotlib. Even thought it helped a few people transition to Python (including myself), it turned out to be a pretty bad idea from a usability point of view. Matthias Bussonnier wrote up a good list of the many things that are wrong with it in “No Pylab Thanks.”
For the 1.0.0 release of IPython in August 2013, all mentions of %pylab
were removed from the examples (in a July 18, 2013 commit) and were replaced by calls to the %matplotlib
magic function, which only enables interactive plotting but does not perform any imports. The %matplotlib
function had already been introduced in a 2013 refactoring to separate the interatice plotting from the imports. The %gui
magic command had already been introduced in 2009 by Brian Granger to “manage the events loops” (hint hint).
Now we know that the (my) confusion with %gui
and %matplotlib
started in 2013.
This analysis refers to IPython 7.8.0 and ipykernel 5.1.2.
Our entry point will be the %matplotlib
magic command. Its source code is in the IPython.core.pylab.py
file. The essential call is to shell.enable_matplotlib(gui)
, which is itself implemented in IPython.core.interactiveshell.InteractiveShell
, and does five things:
- Select the “backend” given the choice of GUI event loop. This is done by calling
IPython.core.pylabtools.find_gui_and_backend(gui)
. It encapsulates the logic to go from a GUI name, like"qt5"
or"tk"
, to a backend name, like"Qt5Agg"
and"TkAgg"
. - Activate matplotlib for interactive use by calling
IPython.core.pylabtools.activate_matplotlib(backend)
, which:- Activates the interactive mode with
matplotlib.interactive(True)
; - Switches to the new backend with
matplotlib.pyplot.switch_backend(backend)
; - Replaces the
matplotlib.pyplot.draw_if_interactive
method with the same method, but wrapped by aflag_calls
decorator, which adds acalled
flag to the method. That flag will be used by the new%run
runner that’s introduced below at point #5;
- Activates the interactive mode with
- Configure inline figure support by calling
IPython.core.pylabtools.configure_inline_support(shell, backend)
. This is where some very interesting stuff happens. It first checks thatInlineBackend
is actually importable fromipykernel.pylab.backend_inline
, otherwise it returns immediately. But if it’s importable and the backend is"inline"
, it:- Imports the
ipykernel.pylab.backend_inline.flush_figures
function, and register it as a callback for the"post_execute"
event of the shell. As we’ll see later, callbacks for"post_execute"
are called after executing every cell; - If the backend was not
"inline"
, it’ll unregister theflush_figures
callback;
- Imports the
- Enable the GUI by calling
shell.enable_gui(gui)
. This method is not implemented in theIPython.core.interactiveshell.InteractiveShell
base class, but rather inIPython.terminal.interactiveshell.TerminalInteractiveShell
. If agui
as specified, it gets the name of theactive_eventloop
and its correspondinginputhook
function usingIPython.terminal.pt_intputhooks.get_inputhook_name_and_func(gui)
. Theactive_eventloop
is just a string, such as'qt'
, but theinputhook
is more interesting. It’s the function to call to start that GUI toolkit’s event loop. Let’s dig further intoget_inputhook_name_and_func(gui)
. That function checks a few things, but it essentially:- Imports the correct
inputhook
function for the chosen GUI by importing it fromIPython.terminal.pt_intputhooks.<gui_mod>
. For example, the Qtinputhook
is imported fromIPython.terminal.pt_intputhooks.qt
. Later on, wheninputhook
is executed for Qt, it will:- Create a
QCoreApplication
; - Create a
QEventLoop
for that application; - Execute the event loop and register the right events to make sure the loop is shut down properly. The exact operations to start and stop the loop are slightly different for other GUI toolkits, like
tk
,wx
, orosx
, but they all essentially do the same thing. At this point we’re ready to go back up the stack toenable_matplotlib
in%matplotlib
;
- Create a
- Imports the correct
- Replace IPython’s
default_runner
with the one defined inIPython.core.pylabtools.mpl_runner
. Thedefault_runner
is the function that executes code when using the%run
magic. Thempl_runner
:- Saves the
matplotlib.interactive
state, and disables it; - Executes the file;
- Restores the
interactive
state; - Makes the rendering call, if the user asked for it, by checking the
plt.draw_if_interactive.called
flag that was introduced at point #1.3 above.
- Saves the
As for the other magic, %gui
, it only executes a subset of what %matplotlib
does. It only calls shell.enable_gui(gui)
, which is point #4 above. This means that if your application requires interaction with a GUI’s event loop, but doesn’t require matplotlib, then it’s sufficient to use %gui
. For example, if you’re writing applications using TraitsUI or PyQt.
The Effect of Calling %gui
and %matplotlib
Let’s start with the “simplest” one, %gui
. If you execute it in a fresh IPython session, it’ll only start the event loop. On macOS, the obvious effect of this is to start the Rocket icon.
At that point, if you import matplotlib and call plt.plot()
, no figure will appear unless you either call plt.show()
afterwards, or manually enable interactive mode with plt.interactive(True)
.
On the other hand, if you start your session by calling %matplotlib
, it’ll start the Rocket and activate matplotlib’s interactive mode. This way, if you call plt.plot()
, your figure will show up immediately and your session will not be blocked.
Using %run
If you call %run my_script.py
after calling %matplotlib
, my_script.py
will be executed with the mpl_runner
introduced above at point #5.
Executing a Jupyter Notebok Cell When Using the "inline"
Backend
In the terminal the IPython.terminal.interactiveshell.TerminalInteractiveShell.interact()
method is where all the fun stuff happens. It prompts you for code, checks if you want to exit, and then executes the cell with InteractiveShell.run_cell(code)
and then trigger the "post_execute"
event for which we’ve registered the ipykernel.pylab.backend_inline.flush_figures
callback. As you might have noticed, the flush_figures
function comes from ipykernel, and not from IPython. It tries to return all the figures produced by the cell as PNG of SVG, displays them on screen using IPython’s display
function, and then closes all the figures, so matplotlib doesn’t end up littered will all the figures we’ve ever plotted.
Conclusion
To sum it up, use %matplotlib
if you want interactive plotting with matplotlib. If you’re only interested in the GUI’s event loop, %gui <backend>
is sufficient._ Although as far as I understand, there’s nothing very wrong with using %matplotlib
all the time.
- Basically, no namespaces, and direct access to functions like
plot
,figure
,subplot
, etc. [return] - The earliest commit I found for the IPyhon project was on July 6, 2005 by Fernando Perez, 7 months after the name change. Its Git hash is 6f629fcc23ba63342548f61cc7307eeef4f55799. But the earliest mention is an August 2004 entry in the ChangeLog: “ipythonrc-pylab: Add matplotlib support,” which is before the offical rename in matplotlib. [return]