I am writing a terminal REPL in macOS, and having a hard time replicating the non-blocking matplotlib plotting behavior that you get with IPython's %matplotlib magic.
Consider the following simple REPL (I am using Python 3.5 from Anaconda on macOS 10.12):
#!/usr/bin/env pythonw
# Basic REPL (note: doesn't show output)
_globals = _locals = globals().copy()
while True:
command = input(">>> ")
res = exec(command, _globals, _locals)
The pythonw is necessary to get the plot to focus correctly.
If you run in this shell
import matplotlib.pyplot as plt
plt.plot([1, 2])
plt.show()
it will block. I've tried
import matplotlib
matplotlib.interactive(True)
import matplotlib.pyplot as plt
plt.plot([1, 2])
This doesn't block, but the plot window that appears is completely unresponsive. I can't even close it. Running
import matplotlib.pyplot as plt
plt.plot([1, 2])
plt.show(block=False)
is the same.
On the other hand, if you run
%matplotlib
import matplotlib.pyplot as plt
plt.plot([1, 2])
in IPython 5.1.0, it works perfectly. The REPL doesn't block, and I can interact with the plot.
I've tried reading the IPython source to figure out what I need to replicate, but I can't figure it out. I've even tried running IPython.core.pylabtools.activate_matplotlib("MacOSX"), but it doesn't work.
I discovered that IPython 4.2.1 actually does this wrong, which allowed me to bisect the IPython codebase and find the answer.
IPython 5, which uses prompt-toolkit, has a special inputhook that it passes to prompt-toolkit's eventloop argument of run_application. IPython's inputhook is defined in IPython.terminal.pt_inputhooks.osx. The code does a bunch of ctypes calls into the macOS APIs (basically, to get the GUI eventloop).
I don't know how to use this for the dummy REPL from my question, but I am actually using prompt-toolkit, so this is fine for me. To use it, use
from IPython.terminal.pt_inputhooks.osx import inputhook
from prompt_toolkit.shortcuts import create_eventloop
# <prompt-toolkit stuff>
...
run_application(eventloop=create_eventloop(inputhook)
You also still do need the matplotlib.interactive(True) call before importing matplotlib.pyplot to make the plots show automatically (otherwise you have to call plt.show() all the time, and, more importantly, the plots will block).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With