简体   繁体   English

为什么PyQt4在Jupyter和IPython Notebook之间的行为有所不同?

[英]Why does PyQt4 behave differently between Jupyter and IPython notebook?

I have created a large python program with GUI based on PyQt4. 我使用基于PyQt4的GUI创建了一个大型python程序。 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. 我希望该程序包既可以在IPython笔记本(Windows上使用python 2.7的旧安装),Jupyter笔记本(最近与Anaconda一起安装的Python 3.5)中运行,也可以在命令行上传递的python程序中运行。 I'm having problems in running the code in Jupyter notebook (see directly at the bottom). 我在Jupyter笔记本中运行代码时遇到问题(直接在底部查看)。

My module mymodule.py looks like this (extremely simplified, about 10k lines before the show from many other python files): 我的模块mymodule.py看起来像这样(极其简化,从许多其他python文件开始显示前大约1万行):

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 与以下文件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. 我了解需要APP.exec_()来启动通过Gui交互事件起作用的某种EventLoop。

In an IPython notebook, the user typically does 在IPython笔记本中,用户通常会

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. 我可以毫无问题地运行此程序,在此我可以确定IPython以某种方式在后台处理了EventLoop。

Now, running the same commands in Jupyter notebook, a window opens, but freezes before allowing any user interaction. 现在,在Jupyter Notebook中运行相同的命令,将打开一个窗口,但在允许任何用户交互之前会冻结。 So I believe that Jupyter notebook does not treat the events properly because I did not tell it to do so. 因此,我相信Jupyter笔记本电脑无法正确处理事件,因为我没有告诉它这样做。 One way I have found is executing the command %pylab before running my code. 我发现的一种方法是在运行代码之前执行命令%pylab 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). 但是,我经常遇到与此相关的问题,例如,在启动程序之前直接连续连续运行%pylab%matplotlib inline ,这会导致在我加载代码后再次冻结(奇怪的是,颠倒了两个魔术命令的顺序)再次工作)。 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). 另外,如果可以避免的话,我也不想强迫我的程序的用户在每个新笔记本中执行%pylab(也是因为我认为这需要安装matlab,而这并不是我的程序的要求)。

What code must I add in mymodule.py to make things compatible with the described user code in Jupyter notebook? 我必须在mymodule.py中添加什么代码,以使其与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? 谁能更清楚地解释IPython笔记本和Jupyter笔记本如何以不同方式管理QEventLoop / QApplication(或此处的重要概念),以及魔术命令如何使它混乱? 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. 这是一个可行的解决方案,它允许运行代码而无需区分不同的IPython / Jupyter版本和“原始” Python。 My __init__.py file contains this section at the beginning: 我的__init__.py文件开头包含以下部分:

# 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: 然后,在原始Python上运行的脚本只需要这样做:

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. 我发现这没有用的用例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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