prompt-toolkit / ptpython

A better Python REPL
BSD 3-Clause "New" or "Revised" License
5.2k stars 277 forks source link

Python process freezes on exiting plot window from matplotlib.pyplot.show() when in ptipython REPL #431

Open Datamance opened 3 years ago

Datamance commented 3 years ago

Issue

This script works perfectly fine when executed via python on the CLI

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Testing things out."""
from matplotlib import pyplot as plt
import numpy as np

x_values = np.arange(1, 20)
y_values = np.sin(x_values)

plt.xlabel("X values")
plt.ylabel("Y values")

plt.plot(x_values, y_values)

plt.show()

It also works when executed piecemeal in the python, ptpython, and ipython REPLs.

However, when executed piecemeal in the ptipython REPL, something strange happens

  1. plt.show() seems to execute and the plot is shown in a GUI window
  2. I click on the close window button
  3. The spinning wheel of death occurs and the python process hangs

After this, I generally have to force quit the plotting process.

Any idea what could be going on here?

Version Info

Using a Poetry virtualenv setup with python 3.8.3

Python

sys.version_info(major=3, minor=8, micro=3, releaselevel='final', serial=0)

ptpython

name : ptpython version : 3.0.13 description : Python REPL build on top of prompt_toolkit

dependencies

ipython

name : ipython version : 7.19.0 description : IPython: Productive Interactive Computing

dependencies

System info

System Version: macOS 11.1 (20C69) Kernel Version: Darwin 20.2.0

Datamance commented 3 years ago

I see that this is an old issue, actually (see #319 and #221)

@jonathanslenders, do you have a good understanding of where/how the issue might be happening? If so, I'll try and take a crack at it and send a PR over.

Datamance commented 3 years ago

Uy, OK - after reading this prompt-toolkit issue I see what's going on, so I'll leave a little summary of what I did for the next unfortunate soul to stumble across this issue:

Background

  1. The root issue is due to a disconnect between the prompt-toolkit event loop and that of IPython
  2. This issue can be bypassed by using the TkAgg backend (matplotlib.use("TkAgg"))
  3. However, Homebrew and pyenv (and, transitively, asdf) seem to leave out tcl-tk integration in default builds - sad!

Solution

The Python that your virtual environment uses will need to be built with the proper integrations. With asdf/pyenv, you can use something along the lines of PYTHON_CONFIGURE_OPTS="--with-tcltk-includes='-I/usr/local/opt/tcl-tk/include' --with-tcltk-libs='-L/usr/local/opt/tcl-tk/lib -ltcl8.6 -ltk8.6'" asdf install python 3.9.1

sphh commented 3 years ago

@Datamance: Is there an update? I run into a similar problem: I want to update a plot based on user's input. If I use input() this works fine, if I use prompt_toolkit.prompt() the plot is not updated and I cannot interact with it until I call input() again.

Here is a simple case to be run from the terminal:

#!/usr/bin/env python3

# import matplotlib
# matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import numpy as np
import prompt_toolkit

x_values = np.arange(1, 20)
y_values = np.sin(x_values)

plt.xlabel("X values")
plt.ylabel("Y values")

plt.plot(x_values, y_values)

plt.show(block=False)

i = 2

print('Lecacy input()')
print('You can interact with the plot window and it updates on ENTER:')
try:
    while True:
        r = input('(Proceed with CTRL-D) > ')
        print(f'You said: {r}')
        plt.plot(x_values, np.sin(x_values/i))
        plt.draw()
        i += 1
except EOFError:
    print()
    pass

print('prompt_toolkit.prompt()')
print('No interaction and no updates')
try:
    while True:
        r = prompt_toolkit.prompt('Proceed with CTRL-D > ')
        print(f'You said: {r}')
        plt.plot(x_values, np.sin(x_values/i))
        plt.draw()
        i += 1
except EOFError:
    pass

print('Calling input() again updates the plot ...')
input('(Exit with any input) > ')

If I uncomment the first two lines thus enabling matplotlib.use('TkAgg') it shows the same blocking behaviour.

Any help how to run those two loops synchronously would be highly appreciated?