Interactive wx/pylab with no threads (PyOS_InputHook)

IPython and matplotlib devs,

Over the weekend I have been playing around to see if it is possible
to do interactive GUI work with wx from IPython *without using
threads*. The idea here is to use PyOS_InputHook. Currently, recent
versions of PyQt4 and PyGTK do this and if we can get wx working, we
can probably get rid of IPython's subtle threaded shells that
currently allow interactive GUIs to work.

I am attaching a Cython module that mostly works. Here is a simple
example that works in IPython (without the -wthread option!)

In [1]: import pyximport

In [2]: pyximport.install()

In [3]: import inputhook

In [4]: inputhook.set_input_hook()

In [5]: import wx

In [6]: app = wx.PySimpleApp()

In [7]: app.MainLoop()

In [8]: f = wx.Frame(None,-1,"Look mom, no threads!")

In [9]: f.Show()
Out[9]: True

The docstring of the module also has a matplotlib example. This
really does work and I am pretty sure it will also work in plain
vanilla python as well. There are a few issues to work out:

* When frame are closed by clicking the red button or the "X", the
Windows don't close. In addition, in matplotlib, this causes further
problems.

* In the current matplotlib backend wx.Yield() is called in a way that
is not safe as far as protecting against recursive calls to Yield. I
think it should be called in this way:

app = wx.GetApp()
if app is not None:
  app.Yield(True)

* I don't think that interupts work yet, but I haven't tested this
thoroughly yet.

I don't have any more time to work on this right now, but I at least
wanted to share my findings with both IPython and matplotlib devs. It
would be great if someone familiar with wx could try to figure out the
remaining issues. If there are no takers here, I might eventually see
if wxpython itself is interested in this code (that is probably where
it really belongs anyway).

Cheers,

Brian

inputhook.h (246 Bytes)

inputhook.pyx (2.33 KB)

The problem I see with this approach is that arbitrary wx programs will
always be doing this. The matplotlib guys can fix matplotib not to do
this. I can fix Mayavi not to do this, but there are many more wx
programs. And anyhow, most of the time, Yield should not be called, as it
is a hack. Unfortunately, you often end up having to call it. :frowning:

Gaël

···

On Sun, Feb 08, 2009 at 04:08:31PM -0800, Brian Granger wrote:

* In the current matplotlib backend wx.Yield() is called in a way that
is not safe as far as protecting against recursive calls to Yield. I
think it should be called in this way:

app = wx.GetApp()
if app is not None:
  app.Yield(True)

Hi Brian,

I wrote the code in PyGTK that uses PyOS_InputHook for interactivity, as well as the Mac OS X native backend for matplotlib that uses PyOS_InputHook in exactly the same way. PyQT and Tkinter also use PyOS_InputHook, though the code is a bit kludgy on Windows. So I definitely agree that PyOS_InputHook is the right way to go.

Your current code should work, but there's a better way to do it. If I understand the code correctly, you rely on the fact that PyOS_InputHook is called repeatedly by readline, and you use PyOS_InputHook to process wx events that need to be processed at that time. A better way is to use PyOS_InputHook to start the wx event loop, but have this event loop check stdin. As soon as some input is available on stdin, you exit the event loop, which means that PyOS_InputHook returns, and Python can proceed to handle the command that was just entered by the user on stdin.

Essentially, think of wx's event loop as sitting in a call to select(), waiting for the next wx event to arrive. You want to add fileno(stdin) to the set of file descriptors watched by select().

There are two advantages to this approach. First, it does not rely on readline calling PyOS_InputHook repeatedly. This is important, since Python may not be using readline at all, and if it is, depending on the Python version and how readline was installed it may call PyOS_InputHook only once. Second, this approach is more efficient (not wasting processor cycles going back and forth between readline and PyOS_InputHook), and gives a better response time (essentially immediate).

The best place to put this code is in wxPython. Hopefully (I haven't checked this), wx exposes enough of the event loop to allow you to have it watch stdin. This may be an issue, since for example qt4 does not on Windows, which is why the event loop is kludgy with PyQT on Windows. You could have a look at the PyOS_InputHook code in PyGTK (you'll need to get the developer's version of PyGTK, since this code is not yet in an official release). It's actually quite straightforward and you may be able to modify it directly for wx.

It's actually unfortunate that we have to use PyOS_InputHook; all this would be a lot easier if Python itself supported event loops.

--Michiel

···

--- On Sun, 2/8/09, Brian Granger <ellisonbg.net@...149...> wrote:

From: Brian Granger <ellisonbg.net@...149...>
Subject: [matplotlib-devel] Interactive wx/pylab with no threads (PyOS_InputHook)
To: matplotlib-devel@lists.sourceforge.net, "IPython Development list" <ipython-dev@...336...>
Date: Sunday, February 8, 2009, 7:08 PM
IPython and matplotlib devs,

Over the weekend I have been playing around to see if it is
possible
to do interactive GUI work with wx from IPython *without
using
threads*. The idea here is to use PyOS_InputHook.
Currently, recent
versions of PyQt4 and PyGTK do this and if we can get wx
working, we
can probably get rid of IPython's subtle threaded
shells that
currently allow interactive GUIs to work.

I am attaching a Cython module that mostly works. Here is
a simple
example that works in IPython (without the -wthread
option!)

In [1]: import pyximport

In [2]: pyximport.install()

In [3]: import inputhook

In [4]: inputhook.set_input_hook()

In [5]: import wx

In [6]: app = wx.PySimpleApp()

In [7]: app.MainLoop()

In [8]: f = wx.Frame(None,-1,"Look mom, no
threads!")

In [9]: f.Show()
Out[9]: True

The docstring of the module also has a matplotlib example.
This
really does work and I am pretty sure it will also work in
plain
vanilla python as well. There are a few issues to work
out:

* When frame are closed by clicking the red button or the
"X", the
Windows don't close. In addition, in matplotlib, this
causes further
problems.

* In the current matplotlib backend wx.Yield() is called in
a way that
is not safe as far as protecting against recursive calls to
Yield. I
think it should be called in this way:

app = wx.GetApp()
if app is not None:
  app.Yield(True)

* I don't think that interupts work yet, but I
haven't tested this
thoroughly yet.

I don't have any more time to work on this right now,
but I at least
wanted to share my findings with both IPython and
matplotlib devs. It
would be great if someone familiar with wx could try to
figure out the
remaining issues. If there are no takers here, I might
eventually see
if wxpython itself is interested in this code (that is
probably where
it really belongs anyway).

Cheers,

Brian
------------------------------------------------------------------------------
Create and Deploy Rich Internet Apps outside the browser
with Adobe(R)AIR(TM)
software. With Adobe AIR, Ajax developers can use existing
skills and code to
build responsive, highly engaging applications that combine
the power of local
resources and data with the reach of the web. Download the
Adobe AIR SDK and
Ajax docs to start building applications
today-http://p.sf.net/sfu/adobe-com_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

Michiel,

Thanks for jumping into the discussion.

I wrote the code in PyGTK that uses PyOS_InputHook for interactivity, as well as the Mac OS X native backend for matplotlib that uses PyOS_InputHook in exactly the same way. PyQT and Tkinter also use PyOS_InputHook, though the code is a bit kludgy on Windows. So I definitely agree that PyOS_InputHook is the right way to go.

Great, I was wondering how the Mac OS X backend works - now I know. I
will have a look at the code for both PyGTK and OS X. Hopefully that
will show me more of the best way of handling this.

Your current code should work, but there's a better way to do it. If I understand the code correctly, you rely on the fact that PyOS_InputHook is called repeatedly by readline, and you use PyOS_InputHook to process wx events that need to be processed at that time.

Yes, at least that is my understanding. I put in some debug
statements and you could see that it was being called repeatedly.

A better way is to use PyOS_InputHook to start the wx event loop, but
have this event loop check stdin. As soon as some input is available
on stdin, you exit the event loop, which means that PyOS_InputHook
returns, and Python can proceed to handle the command that was just
entered by the user on stdin.

Essentially, think of wx's event loop as sitting in a call to select(), waiting for the next wx event to arrive. You want to add fileno(stdin) to the set of file descriptors watched by select().

I have seen that this is how the PyQt4 implementation handles it.

There are two advantages to this approach. First, it does not rely on readline calling PyOS_InputHook repeatedly. This is important, since Python may not be using readline at all, and if it is, depending on the Python version and how readline was installed it may call PyOS_InputHook only once.

OK, I was wondering about this. But, what happens if PyOS_InputHook
is called repeatedly. Are you not then starting the event loop
multiple times. Can you say more about what happens in this case?

Second, this approach is more efficient (not wasting processor cycles
going back and forth between readline and PyOS_InputHook), and gives a
better response time (essentially immediate).

That would be very nice as my implementation is less responsive.

The best place to put this code is in wxPython. Hopefully (I haven't checked this), wx exposes enough of the event loop to allow you to have it watch stdin. This may be an issue, since for example qt4 does not on Windows, which is why the event loop is kludgy with PyQT on Windows. You could have a look at the PyOS_InputHook code in PyGTK (you'll need to get the developer's version of PyGTK, since this code is not yet in an official release). It's actually quite straightforward and you may be able to modify it directly for wx.

Yes, I fully agree with this. I might end up contacting the wx devs
to get their help on this. I actually don't know wx at all, so I am
amazed that I got this far. I will have a look at the PyGTK
implementation.

It's actually unfortunate that we have to use PyOS_InputHook; all this would be a lot easier if Python itself supported event loops.

Yes, that would be nice!!!

Cheers,

Brian

···

--Michiel

--- On Sun, 2/8/09, Brian Granger <ellisonbg.net@...149...> wrote:

From: Brian Granger <ellisonbg.net@...149...>
Subject: [matplotlib-devel] Interactive wx/pylab with no threads (PyOS_InputHook)
To: matplotlib-devel@lists.sourceforge.net, "IPython Development list" <ipython-dev@...336...>
Date: Sunday, February 8, 2009, 7:08 PM
IPython and matplotlib devs,

Over the weekend I have been playing around to see if it is
possible
to do interactive GUI work with wx from IPython *without
using
threads*. The idea here is to use PyOS_InputHook.
Currently, recent
versions of PyQt4 and PyGTK do this and if we can get wx
working, we
can probably get rid of IPython's subtle threaded
shells that
currently allow interactive GUIs to work.

I am attaching a Cython module that mostly works. Here is
a simple
example that works in IPython (without the -wthread
option!)

In [1]: import pyximport

In [2]: pyximport.install()

In [3]: import inputhook

In [4]: inputhook.set_input_hook()

In [5]: import wx

In [6]: app = wx.PySimpleApp()

In [7]: app.MainLoop()

In [8]: f = wx.Frame(None,-1,"Look mom, no
threads!")

In [9]: f.Show()
Out[9]: True

The docstring of the module also has a matplotlib example.
This
really does work and I am pretty sure it will also work in
plain
vanilla python as well. There are a few issues to work
out:

* When frame are closed by clicking the red button or the
"X", the
Windows don't close. In addition, in matplotlib, this
causes further
problems.

* In the current matplotlib backend wx.Yield() is called in
a way that
is not safe as far as protecting against recursive calls to
Yield. I
think it should be called in this way:

app = wx.GetApp()
if app is not None:
  app.Yield(True)

* I don't think that interupts work yet, but I
haven't tested this
thoroughly yet.

I don't have any more time to work on this right now,
but I at least
wanted to share my findings with both IPython and
matplotlib devs. It
would be great if someone familiar with wx could try to
figure out the
remaining issues. If there are no takers here, I might
eventually see
if wxpython itself is interested in this code (that is
probably where
it really belongs anyway).

Cheers,

Brian
------------------------------------------------------------------------------
Create and Deploy Rich Internet Apps outside the browser
with Adobe(R)AIR(TM)
software. With Adobe AIR, Ajax developers can use existing
skills and code to
build responsive, highly engaging applications that combine
the power of local
resources and data with the reach of the web. Download the
Adobe AIR SDK and
Ajax docs to start building applications
today-http://p.sf.net/sfu/adobe-com_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

I will have a look at the code for both PyGTK and OS X.
Hopefully that will show me more of the best way of
handling this.

The code in PyGTK is a bit easier to understand than the code for OS X. The OS X code also includes stuff to handle SIGINTs (keyboard interrupts by ctrl-c), which is nice but not really essential.

But, what happens if PyOS_InputHook is called
repeatedly. Are you not then starting the event loop
multiple times. Can you say more about what happens in
this case?

On the first call to PyOS_InputHook, you start the event loop, and you stay in the event loop until some input is available on stdin. So Python never gets the chance to call PyOS_InputHook repeatedly.
Depending on if and how readline is installed, it is possible that once input is available on stdin and PyOS_InputHook exits, PyOS_InputHook is called a second time. But that doesn't do any real damage: the event loop is restarted, but it exits immediately because input is available on stdin. It's good to double-check that that works on wx though. Another option is to write your hook function as follows:

check if any input is available on stdin; if so, return

add fileno(stdin) to the set of file descriptors to be watched by the event loop

start the event loop; if input is available on stdin, exit the event loop and return

--Michiel.

Just nit picking: You'd really have to modify traits and pyface for this. Mayavi doesn't start the mainloop.

cheers,
prabhu

···

On 02/09/09 11:59, Gael Varoquaux wrote:

On Sun, Feb 08, 2009 at 04:08:31PM -0800, Brian Granger wrote:

* In the current matplotlib backend wx.Yield() is called in a way that
is not safe as far as protecting against recursive calls to Yield. I
think it should be called in this way:

app = wx.GetApp()
if app is not None:
  app.Yield(True)

The problem I see with this approach is that arbitrary wx programs will
always be doing this. The matplotlib guys can fix matplotib not to do
this. I can fix Mayavi not to do this, but there are many more wx
programs. And anyhow, most of the time, Yield should not be called, as it
is a hack. Unfortunately, you often end up having to call it. :frowning:

IPython and matplotlib devs,

Over the weekend I have been playing around to see if it is possible
to do interactive GUI work with wx from IPython *without using
threads*. The idea here is to use PyOS_InputHook. Currently, recent
versions of PyQt4 and PyGTK do this and if we can get wx working, we
can probably get rid of IPython's subtle threaded shells that
currently allow interactive GUIs to work.

I am attaching a Cython module that mostly works. Here is a simple
example that works in IPython (without the -wthread option!)

[...]

I don't have any more time to work on this right now, but I at least
wanted to share my findings with both IPython and matplotlib devs. It
would be great if someone familiar with wx could try to figure out the
remaining issues. If there are no takers here, I might eventually see
if wxpython itself is interested in this code (that is probably where
it really belongs anyway).

This is cool! It works with mayavi, which is a pretty demanding test. I did run into problems with pyximport messing up on some imports but manually importing the inputhook.so fixed those. The interactive response is not snappy but it definitely works without a problem.

One problem I can see with this is that while it does eliminate the threading, there are still issues with multiple toolkits. It would be neat if there were a system where you could mix toolkits too -- it looks like it should be possible to support this though.

cheers,
prabhu

···

On 02/09/09 05:38, Brian Granger wrote: