Why does PyQt4 behave differently between Jupyter and IPython notebook? - python

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.

Related

Win10 starting Python PyQt5 application using shortcut

Simply stated I have a simple python application which generates random passwords. This application was originally written using Tkinter and currently works. I am trying to improve the GUI interface by employing PyQt5. My efforts, so far have resulted in an application that runs from within my IDE (Spyder) and can also be run by invoking python from the commandline with the fullpath of the python script.
It should be noted this works for the Tkinter as well as the PyQt implementation.
My next step was to define a shortcut on the desktop to execute this script and have a window appear allowing creation of a password. The shortcut for the Tkinter script performs as expected and results in a window appearing. The script for the PyQt5 based script does not work. The only differences between the scripts are the target files being invoked by the script. Also both script files are in the same directory. This is a side by side image of the shortcut properties.
Here is a very simplistic example of the Puqt5 code. This code exhibits the same characteristics as the original in that it runs in the IDE as well as directly from Python in the CMD window but will not execute from a shortcut icon.
"""
Created on Wed Sep 9 10:37:46 2020
"""
import sys
from PyQt5.QtWidgets import QApplication, QWidget
def main():
app = QApplication(sys.argv)
w = QWidget()
w.resize(250, 150)
w.move(300, 300)
w.setWindowTitle('Simple')
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Has anyone else had this type of problem or can anyone suggest an approach for determining what is the cause and solution
everything works on Windows 7
It work when invoked from a shortcut on Win &, hmm that's very strange.
I tested it on Windows 10 also works.
Thanks for the helpful suggestions as well as your efforts to test this problem on your own systems. I finally found an answer that satisfies my needs. I used pyinstaller to create an executable and then launch the executable from a desktop shortcut. This works, so I am considering my question closed.

Unable to see app windows created by PySide2 in Spyder

I'm trying to run an app with PySide2 from Spyder 3.2.8 and Python 3.6.4 in Anaconda in a macOS 10.13.4.
attempt N°1
After having seen this stackoveflow page and this github page I changed my graphic backend from Inline to Automatic in Python > Preferences > IPython Console > Graphics and I tried to run the following script (script N°1):
script N°1
import sys
from PySide2.QtWidgets import *
# Create a Qt application
app = QApplication.instance()
if app is None:
print("print something")
app = QApplication(sys.argv)
# Create a Label and show it
label = QLabel("Hello World")
label.show()
# Enter Qt application main loop
app.exec_()
but got the following error message after running it:
Importing PySide2 disabled by IPython, which has
already imported an Incompatible QT Binding: pyqt5
There are similar reported issues here with matplotlib and here with ipython but it didn't help me (or I couldn't implement it properly). Then I tried to implement the content of this page about qtpy by changing the script N°1 in the following way:
script N°2
import os
os.environ['QT_API'] = 'pyside2'
from qtpy.QtWidgets import *
import sys
# Create a Qt application
app = QApplication.instance()
if app is None:
print("print something")
app = QApplication(sys.argv)
# Create a Label and show it
label = QLabel("Hello World")
label.show()
# Enter Qt application main loop
app.exec_()
attempt N°2
With Inline selected in Python > Preferences > IPython Console > Graphics. When I ran the script N°2 , the app launches and I got print something printed in to the console. When closing the app, I got Out[1]: 0 in the console. However when I run the script again, no error message appears in the console but the window of the app doesn't show-up
attempt N°3
This time with Automatic selected in Python > Preferences > IPython Console > Graphics. When I ran the script N°2 the first time, the app didn't launch and I got the following error message
/anaconda3/lib/python3.6/site-packages/qtpy/__init__.py:178: RuntimeWarning: Selected binding "pyside2" could not be found, using "pyqt5"
'using "{}"'.format(initial_api, API), RuntimeWarning)
Out[2]: -1
attempt N°4
With Automatic selected in Python > Preferences > IPython Console > Graphics. When I ran the script N°1 after having changed the line from PySide2.QtWidgets import * to from PyQt5.QtWidgets import *: The app didn't launch and I got the following error message
Out[1]: -1
attempt N°5
With Inline selected in Python > Preferences > IPython Console > Graphics. When I ran the script N°1 after having changed the line from PySide2.QtWidgets import * to from PyQt5.QtWidgets import *: The app launches and I got print something printed in to the console. I closed the app and got Out[1]: 0 in the console. However when I run the script again, no error message appears in the console but the window of the app doesn't show-up
N.B. this question is the continuation of that question
(Spyder maintainer here) Since the ipykernel package (which is used by Spyder to run code in its consoles) doesn't have event loop support for PySide2 as of May/2018 (as can be seen here), you won't be able to run PySide2 code inside Spyder, no matter what you try.
Notes:
The Automatic backend tries to select a suitable event loop for you, in this order: Qt5, Qt4, Tk and Inline. That's why it doesn't work in your case.
Every time you change a Graphics backend in Spyder, you need to restart the kernel of the console you want to run your code in. That's because you can only use one backend per console session (this is a limitation imposed by ipykernel, not by us). It's clear from your question that you're not doing that.
We're aware we fail to inform users when a kernel restart is necessary. We'll try to address that in our next major version (Spyder 4), to be released in 2019.
If you already know about qtpy, please use it to develop your apps instead of using PySide2 directly. That way you could work with PyQt5 for development in Spyder, but PySide2 for deployment, since qtpy takes care of working seamlessly with whatever binding is available.

PyDev doesn't recognise PyQt5

I am following a tutorial on pyqt, and got this code:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
cb = QCheckBox('Show title', self)
cb.move(20, 20)
cb.toggle()
cb.stateChanged.connect(self.changeTitle)
self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Checkbox')
self.show()
def changeTitle(self, state):
if state == Qt.Checked:
self.setWindowTitle('Checkbox')
else: self.setWindowTitle('Unchecked!')
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I'm using PyDev on Eclipse. Suffice it to say that the code runs fine, but what is awkward is that PyDev underlines anything Qt/Q with a red line which when hovered over says Undefined variable: <..>. If it is undefined then how is it that my code runs without errors? Clearly this ought to be a problem with PyDev. I've removed the python interpreter (it was pointing to python2.7 instead of 3.4) and readded it as the correct version; but that didn't work. Interestingly enough, it recognises PyQt4 and insists on using widgets from that instead of PyQt5.
Just so you guys are aware, the code sample above is from another laptop which had PyQt5 as well. Both projects were from PyDev, and both had Ubuntu 15.04. It's possible that my importing of the project on my current machine messed up PyDev parsing the required libraries. Does anyone have a solution as to why PyDev doesn't recognise PyQt5?
I had the same problem. These steps worked for me.
Set the environment variable: export QT_API=pyqt5 (or whatever as appropriate)
restart eclipse so that picks up the new environment setting, and then add PyQt5 to the list of forced builtins for the interpreter (Window->preferences->pydev->interpreters->python interpreters) or look here http://www.pydev.org/manual_101_interpreter.html for more details.
The following SO question tipped me off to the presence of the variable: Setting up IPython Qtconsole with PyQt5. Before I set it, I as able to get some completion to work just by adding 'PyQt5' to the builtins, but it would not, for example, provide the full list of completions to something likefrom PyQt5.QtGui import, even though ipython stand-alone would. Further, the python console in pydev had the same problem and calling module_completion("from PyQt5.QtGui import Q") from Ipython.core.completerlib produced the same incomplete list. In the end, I guessed that since pydev was loading PyQt4 for the gui event loop (also configurable in the interpreter settings), there was a namespace conflict when it tried to introspect the Qt5 modules, causing it to bail out before it could build the full list of completions. Setting the environment variable causes pydev to load pyqt5 instead of the default pyqt4. I haven't checked, but it seems likely that set this way pydev will have problems completing pyqt4 references.
For all those lonesome internet wanderers trying to figure out how to integrate eclipse, pydev, and pyqt5 on Linux, I bring you my method from start to finish.
Eclipse, PyQt5, and PyDev on Linux
Install python v3.6
Install eclipse from eclipse.org
In eclipse, click Help->Install New Software
Click Add...
Add in software source "http://www.PyDev.org/updates" to the available software sources
Call it PyDev
Click on PyDev checkbox
Install it by clicking Next
Download PyQt5
Download SIP
Install SIP first
Install PyQt5
Reconfigure eclipse to use PyQt5
Click on Window→Preferences→PyDev→Interpreters→Python Interpreters
Click on Advanced Auto-Config
Rename interpreter to “python3.6”
Click on Libraries tab
Click on New Folder
Add in “/usr/lib/x86_64-linux-gnu/qt5/plugins”
Add in “/usr/lib/x86_64-linux-gnu/qt5/libexec”
Add in “/usr/lib/x86_64-linux-gnu/qt5/bin”
Click Apply
Click Apply and Close
Restart eclipse
Profit!
This will allow you to get the tab code completion in eclipse when developing pyqt5 applications.

QApplication instance causing python shell to be sluggish

My IPython shell becomes sluggish after I instantiate a QApplication object. For example, even from a fresh start, the following code will make my shell sluggish enough where I have to restart it.
from PyQt4 import QtGui
app = QtGui.QApplication([])
As soon as that is submitted, my typing becomes lagged by 2 or 3 seconds. My computer is not fantastic, but I still have plenty of available memory, and it's only the python shell that seems to be affected. I've tried both the default python interpreter and the ipython interpreter with the same results. Any suggestions?
Update: I also tried running a standalone pyqt "Hello World" program in ipython using the %run magic command and when control was returned to ipython after I closed the resulting "Hello World" window, it had the same effect; the shell became sluggish and my typing starting lagging by 2-3 seconds.
This may help:
QtCore.pyqtRemoveInputHook()
When the QtCore module is imported for the first time it installs a
Python input hook (ie. it sets the value of Python's PyOS_InputHook
variable). This allows commands to be entered at the interpreter
prompt while the application is running. It is then possible to
dynamically create new Qt objects and call the methods of any existing
Qt object.
The input hook can cause problems for certain types of application,
particularly those that provide a similar facility through different
means. This function removes the input hook installed by PyQt.
The input hook can be restored using the pyqtRestoreInputHook()
function.
http://www.riverbankcomputing.com/static/Docs/PyQt4/html/qtcore.html#pyqtRemoveInputHook

How to switch to a python subprocess created by IPython (on OS X)?

When I use IPython along with the -wthread option, it spawns a python subprocess, which appears as a Mac OS X application.
My problem is that when I send commands to that application (for example plotting with matplotlib), the window is updated behind all my other windows. I would like to be able to call a python command to switch this python window to the front (I do that manually with ⌘-tab, but I have to find the python application first, and there might be several ones).
Is there a python script to detect which application IPython has spawned, and how to then automatically switch to it in OS X?
(I'm stating the problem in OS X, but the issue should be similar on other systems).
Edit: let me break this down in two problems:
how to know which Mac OS X application python is running in? (probably possible with some IPython witchery)
how to tell Mac OS X to put the focus on that application? (maybe using applescript)
Could be either:
Making a new python script that tracks grandchild processes of another script might be tricky. The IPython documentation has an example to monitor spawned processes by pid; JobControl. JobControl only kills the processes but I imagine adding a command to change window focus would be fairly easy.
From what I've read, the Tk gui does not properly set window focus on macs. If your 'matplotlib' or otherwise uses the Tk gui, this may be the problem. -source-
I am not very familiar with OS X, so either run with those, clarify your situation or let me know if I'm too far off.
Here is my full solution, with an IPython magic function.
Install appscript (see this question about switching apps programmatically in OS X), and put the following code in a script called activate.py in your ~/.ipython folder.
import appscript
import sys
appscript.app(pid=int(sys.argv[1])).activate()
Now, edit your ~/.ipython/ipy_user_conf.py configuration file and define the magic function:
def wxactivate(self, arg):
import wx
pid = wx.GetProcessId()
ip = self.api
import os
here = os.path.dirname(__file__)
import subprocess
subprocess.Popen([os.path.join(here, 'activate.py'), str(pid)])
Now you just have to register this magic IPython function by putting the following somewhere in that same configuration file:
ip.expose_magic('wxactivate', wxactivate)
Now, after you run IPython -wthread, you can call %wxactivate and you will switch to the corresponding Python application!
(note that the reason why one has to run the call to appscript's activate() in another process in not clear to me; it may have to do with some threading problem... any explanation would be appreciatated)

Categories