Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build a Python REPL with non-blocking matplotlib (like IPython's %matplotlib) on macOS

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.

like image 991
asmeurer Avatar asked Jan 19 '26 18:01

asmeurer


1 Answers

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

like image 173
asmeurer Avatar answered Jan 22 '26 12:01

asmeurer