简体   繁体   中英

Why does PyQt4 behave differently between Jupyter and IPython notebook?

I have created a large python program with GUI based on PyQt4. I would like the package to run both in an IPython notebook (old installation with Python 2.7 on windows), Jupyter notebook (Python 3.5 installed recently with Anaconda), and as a python program passed on the command line. I'm having problems in running the code in Jupyter notebook (see directly at the bottom).

My module mymodule.py looks like this (extremely simplified, about 10k lines before the show from many other python files):

from PyQt4 import QtCore, QtGui

class MyModule(object):
    def __init__(self):
        self.window = QtGui.QMainWindow()
        self.window.show()

The typical usage from command line is

python myscript.py

with the following file myscript.py

from PyQt4 import QtCore, QtGui
import mymodule

m = mymodule.MyModule()

APP = QtGui.QApplication.instance()
APP.exec_()

This works fine. I understand that APP.exec_() is needed to start some kind of EventLoop that works through the Gui interaction events.

In an IPython notebook, the user typically does

import mymodule
m = mymodule.MyModule() # opens the gui
# this still leaves the console active to allow things like this:
m.change_color("red")

I can run this without problems, where I understant that IPython somehow takes care of the EventLoop behind the scene.

Now, running the same commands in Jupyter notebook, a window opens, but freezes before allowing any user interaction. So I believe that Jupyter notebook does not treat the events properly because I did not tell it to do so. One way I have found is executing the command %pylab before running my code. However, I frequently run into related problems with this, for example when running %pylab and %matplotlib inline in direct succession before starting my program, this leads to freezing again once I load my code (curiously, reversing the order of the two magical commands works again). Also, I do not want to force the user of my program to execute %pylab in each new notebook if it can be avoided (also because I believe this requires a matlab installation, which is not a requirement of my program).

What code must I add in mymodule.py to make things compatible with the described user code in Jupyter notebook? Can anyone explain more clearly how IPython notebook and Jupyter notebook manage the QEventLoop/QApplication (or whatever is the important concept here) differently, and how the magic commands mess with this? I am afraid of hidden bugs in my program because of this, and would like to make it as robust as possible to not frustrate the users.

Here is a working solution that allows to run the code without distinguishing between different IPython / Jupyter versions and 'raw' Python. My __init__.py file contains this section at the beginning:

# enable IPython QtGui support if needed
try:
    from IPython import get_ipython
    get_ipython().magic('gui qt')
except BaseException as e:
    # issued if code runs in bare Python
    print('Could not enable IPython gui support: %s.' % e)

# get QApplication instance
from PyQt4 import QtCore, QtGui
APP = QtGui.QApplication.instance()
if APP is None:
    print('Creating new QApplication instance "mymodule"')
    APP = QtGui.QApplication(['mymodule'])

The script running on raw Python then only needs this:

import mymodule  # imports the above code
from PyQt4 import QtCore, QtGui
if __name__ == '__main__':
    QtGui.QApplication.instance().exec_()

I found no use case where this does not work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM