PySide crashing Python when emitting None between threads - python

[edit] This is not a pure duplicate of the PySide emit signal causes python to crash question. This question relates specifically to a (now) known bug in PySide preventing None from being passed across threads. The other question relates to hooking up signals to a spinner box. I've updated the title of this question to better reflect the problem I was facing. [/edit]
I've banged my head against a situation where PySide behaves subtly different from PyQt. Well, I say subtly but actually PySide crashes Python whereas PyQt works as I expect.
I'm completely new to PySide and still fairly new to PyQt so maybe I'm making some basic mistake, but damned if I can figure it out... really hoping one of you fine folks can give some pointers!
The full app is a batch processing tool and much too cumbersome to describe here, but I've stripped the problem down to its bare essentials in the code-sample below:
import threading
try:
# raise ImportError() # Uncomment this line to show PyQt works correctly
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
QtCore.Signal = QtCore.pyqtSignal
QtCore.Slot = QtCore.pyqtSlot
class _ThreadsafeCallbackHelper(QtCore.QObject):
finished = QtCore.Signal(object)
def Dummy():
print "Ran Dummy"
# return '' # Uncomment this to show PySide *not* crashing
return None
class BatchProcessingWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self, None)
btn = QtGui.QPushButton('do it', self)
btn.clicked.connect(lambda: self._BatchProcess())
def _BatchProcess(self):
def postbatch():
pass
helper = _ThreadsafeCallbackHelper()
helper.finished.connect(postbatch)
def cb():
res = Dummy()
helper.finished.emit(res) # `None` crashes Python under PySide??!
t = threading.Thread(target=cb)
t.start()
if __name__ == '__main__': # pragma: no cover
app = QtGui.QApplication([])
BatchProcessingWindow().show()
app.exec_()
Running this displays a window with a "do it" button. Clicking it crashes Python if running under PySide. Uncomment the ImportError on line 4 to see PyQt* correctly run the Dummy function. Or uncomment the return statement on line 20 to see PySide correctly run.
I don't understand why emitting None makes Python/PySide fail so badly?
The goal is to offload the processing (whatever Dummy does) to another thread, keeping the main GUI thread responsive. Again this has worked fine with PyQt but clearly not so much with PySide.
Any and all advice will be super appreciated.
This is under:
Python 2.7 (r27:82525, Jul 4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32
>>> import PySide
>>> PySide.__version_info__
(1, 1, 0, 'final', 1)
>>> from PyQt4 import Qt
>>> Qt.qVersion()
'4.8.2'

So, if the argument is that PySide is neglected and this really is a bug, we might as well come up with a workaround, right?
By introducing a sentinel to replace None, and emitting it the problem can be circumvented, then the sentinel just has to be swapped back to None in the callbacks and the problem is bypassed.
Good grief though. I'll post the code I've ended up with to invite further comments, but if you got better alternatives or actual solutions then do give a shout. In the meantime I guess this'll do:
_PYSIDE_NONE_SENTINEL = object()
def pyside_none_wrap(var):
"""None -> sentinel. Wrap this around out-of-thread emitting."""
if var is None:
return _PYSIDE_NONE_SENTINEL
return var
def pyside_none_deco(func):
"""sentinel -> None. Decorate callbacks that react to out-of-thread
signal emitting.
Modifies the function such that any sentinels passed in
are transformed into None.
"""
def sentinel_guard(arg):
if arg is _PYSIDE_NONE_SENTINEL:
return None
return arg
def inner(*args, **kwargs):
newargs = map(sentinel_guard, args)
newkwargs = {k: sentinel_guard(v) for k, v in kwargs.iteritems()}
return func(*newargs, **newkwargs)
return inner
Modifying my original code we arrive at this solution:
class _ThreadsafeCallbackHelper(QtCore.QObject):
finished = QtCore.Signal(object)
def Dummy():
print "Ran Dummy"
return None
def _BatchProcess():
#pyside_none_deco
def postbatch(result):
print "Post batch result: %s" % result
helper = _ThreadsafeCallbackHelper()
helper.finished.connect(postbatch)
def cb():
res = Dummy()
helper.finished.emit(pyside_none_wrap(res))
t = threading.Thread(target=cb)
t.start()
class BatchProcessingWindow(QtGui.QDialog):
def __init__(self):
super(BatchProcessingWindow, self).__init__(None)
btn = QtGui.QPushButton('do it', self)
btn.clicked.connect(_BatchProcess)
if __name__ == '__main__': # pragma: no cover
app = QtGui.QApplication([])
window = BatchProcessingWindow()
window.show()
sys.exit(app.exec_())
I doubt that'll win any awards, but it does seem to fix the issue.

Related

PyQt5 - stuck in a loop while trying to close ui using macOS

I have created a UI with PyQt5. I can use it on Windows and it works perfectly, but when I try to use it on MacOS I get stuck trying to close it (with self.close()). Using the PyCharm debugger I found out that after self.close() it jumps to app.exec_() and the function that was entered to close it is executed again (for example on_later_button_clicked(self)). I have also already tried sys.exit(app.exec_()).
Here is my code:
import os
import sys
from PyQt5 import QtGui, QtWidgets
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QDialog
from PyQt5.uic import loadUi
from Modules.database import addNeverID
from Modules.supportedWebsites import getWebsites
def Start():
m = askForPartnerUrl()
# m.setFixedSize(500,500)
m.show()
return m
class askForPartnerUrl(QDialog):
def __init__(self):
super(askForPartnerUrl, self).__init__()
loadUi('lib/askForPartnerURL.ui', self)
self.setWindowTitle('Upload')
current_id = getFromFile("id.txt")
self.show_id.setText(current_id)
self.show_origin_url.setText(
'' + getFromFile("origin_url.txt") + '')
self.show_origin_url.setOpenExternalLinks(True)
id_beginns = ["1"]
website_eq = ["1"]
website_guess_str = "Nicht verfügbar!"
for i in range(len(id_beginns)):
if id_beginns[i] in current_id:
website_guess_str = '' + website_eq[i] + ''
self.website_guess.setOpenExternalLinks(True)
break
self.website_guess.setText(website_guess_str)
self.save_button.clicked.connect(self.on_save_button_clicked)
self.later_button.clicked.connect(self.on_later_button_clicked)
self.never_button.clicked.connect(self.on_never_button_clicked)
try:
os.remove('temp/currentObject/partner_url.txt')
except:
pass
#pyqtSlot()
def on_never_button_clicked(self):
addNeverID(getFromFile("id.txt"))
saveToFile("Never-ID", "partner_url.txt")
self.close()
def on_later_button_clicked(self):
saveToFile("Later-ID", "partner_url.txt")
self.close()
def on_save_button_clicked(self):
url_is_valid = False
for i in getWebsites():
if i in self.partner_url_input.text():
url_is_valid = True
break
if url_is_valid:
saveToFile(self.partner_url_input.text(), "partner_url.txt")
self.close()
else:
error_dialog = QtWidgets.QErrorMessage(self)
error_dialog.setWindowTitle("Eingabe nicht verwertbar")
error_dialog.showMessage('Die eingegebene URL ist nicht verwendbar! Bitte prüfe deine Eingabe.')
def showGUI():
app = QApplication(sys.argv)
app.setStyle('Fusion')
app.setWindowIcon(QtGui.QIcon('lib/icon.png'))
window = Start()
app.exec_()
def saveToFile(content, filename):
file = open("temp/currentObject/" + filename, "w+")
file.write(content)
file.close()
def getFromFile(filename):
file = open("temp/currentObject/" + filename)
content = file.read()
file.close()
return content
Many thanks in advance
The reason is that since you're using uic, it automatically enables the auto-connection feature, which automatically detects function names based on object/signals names and connects them, even if the functions do not have Qt slots decorators.
The result is that your slot will be actually called thrice:
without any argument (clicked());
with the checked argument (clicked(bool)): the argument is ignored by Qt since the function doesn't take any, but the function will be called anyway because no slot signature has been specified for it;
again with the checked argument, because you manually connected it in your code;
If you want to keep using the auto connection, use a unique slot decorator for that specific function, otherwise manually connect to a function (possibly with a slot, if you need a specific signature) that does not use the auto connection naming, but don't use both.
class askForPartnerUrl(QDialog):
def __init__(self):
super(askForPartnerUrl, self).__init__()
loadUi('askForPartnerURL.ui', self)
# ...
# remove the following lines:
# self.save_button.clicked.connect(self.on_save_button_clicked)
# self.later_button.clicked.connect(self.on_later_button_clicked)
# self.never_button.clicked.connect(self.on_never_button_clicked)
# manual connection
self.later_button.clicked.connect(self.saveLater)
# using the auto connection; the function doesn't need arguments, so
# you can ignore the argument type signature
#pyqtSlot()
def on_never_button_clicked(self):
addNeverID(getFromFile("id.txt"))
# ...
# with a normal function; in this case no slot decorator is required since
# you don't have arguments
def saveLater(self):
url_is_valid = False
# ...
PS: The reason for which it gets "stuck" is probably due to the way Python deals with the end of the program (which by default happens as soon as the last window is closed in Qt) on MacOS: after the first call to close() PyQt tries to quit the QApplication (free up memory, etc...), but while doing so the original click event is still in the process of firing the signals to the remaining second and third slot, hence the "loop" (but it's not an actual loop, and the third slot never gets called because it's the second one that blocks everything).
Note that this is a big oversimplification, I'm not an expert in memory usage and low level programming, but this is fundamentally what's happening.

PySide2 signal not catched between threads with objects

I'm working on a GUI application, developed in Python and its UI library : PySide2 (Qt wrapper for Python)
I have a heavy computation function I want to put on another thread in order to not freeze my UI. The Ui should show "Loading" and when the function is over, receive from it it's results and update the UI with it.
I've tried a lot of different codes, a lot of examples are working for others but not me, is it PySide2 fault ? (For example this is almost what I want to do : Updating GUI elements in MultiThreaded PyQT)
My code is :
class OtherThread(QThread):
def __init__(self):
QThread.__init__(self)
def run(self):
print 'Running......'
self.emit(SIGNAL("over(object)"), [(1,2,3), (2,3,4)])
#Slot(object)
def printHey( obj):
print 'Hey, I\'ve got an object ',
print obj
thr = OtherThread()
self.connect(thr,SIGNAL("over(object)"),printHey)
thr.start()
My code is working if I use primitives such as bool or int but not with object. I see 'Running....' but never the rest.
Hope someone can enlighten me
You can't define signals dynamically on a class instance. They have to be defined as class attributes. You should be using the new-style signals and slot syntax.
class OtherThread(QThread):
over = QtCore.Signal(object)
def run(self):
...
self.over.emit([(1,2,3), (2,3,4)])
class MyApp(QtCore.QObject)
def __init__(self):
super(MyApp, self).__init__()
self.thread = OtherThread(self)
self.thread.over.connect(self.on_over)
self.thread.start()
#QtCore.Slot(object)
def on_over(self, value):
print 'Thread Value', value

PySide.QtGui RuntimeError: '__init__' method of object's base class not called ...but it was

Some environment basics
Python Version: 3.4.2
OS: Windows 8.1
Searching so far, I suspect this other question is related to my issue at hand, but I'm not sure how I'm replicating enough of the same conditions--probably my lack of in-depth python knowledge.
Simplified code to reproduce issue:
Base Class
from PySide.QtGui import *
class Interface(QWidget):
'''
Wrapper base class for GUI input QWidgets:
- buttons
- text fields
- checkboxes
- line edit
- dropdown menu (combo box)
'''
def __init__(self, parent, name, title_txt=None, qt_obj=None,
update_log_method=None):
print('Interface base class constructor has been called.') #DEBUG
self._parent = parent
self.title = None
self.name = name #also text that appears on component
self.qt_obj = qt_obj
self.inheritted_log_method = update_log_method
# don't want to create an empty text QLabel, or one with
# the text reading "None".
if title_txt:
self.title = QLabel(text=title_txt, parent=parent)
print('Interface base class constructor has been completed.') #DEBUG
def get_name(self):
return self.name
def update_log(self, message, level="INFO"):
''' '''
self.inheritted_log_method(message, level)
Inheriting Class
class IFPushButton(Interface):
''' '''
def __init__(self, name, parent, icon=None, update_log_method=None):
''' '''
# print('\n\nCHECKPOINT: pre IFPushButton super()\n\n') #DEBUG
super(IFPushButton, self).__init__(
parent=parent,
name=name,
qt_obj=QPushButton(icon, name, parent),
update_log_method=update_log_method)
self.behaviors = {}
self.qt_obj.clicked.connect(self.activate)
Something to kick it all off
if __name__ == '__main__':
# setup
import sys
app = QApplication(sys.argv)
qmw = QMainWindow()
qcw = QWidget() #central widget
qcl = QVBoxLayout(qcw) #central layout
# experimental
name = 'named button'
ifpb = IFPushButton(name=name, parent=None, icon=None, update_log_method=None)
print("as long a I don't touch the ifpb instance, everything seems to be okay.")
print("...but the second I do...")
qcl.addWidget(ifpb)
self.show()
print("name of created push button:", ifpb.get_name())
# proper teardown
sys.exit(app.exec_())
I run this all inside one module, interface.py, and when I run it...
C:\Path\To\Module> python interface.py
Interface base class constructor has been called.
Interface base class constructor has been completed.
as long a I don't touch the ifpb instance, everything seems to be okay.
...but the second I do...
Traceback (most recent call last):
File "c_interface.py", line 167, in <module>
qcl.addWidget(ifpb)
RuntimeError: '__init__' method of object's base class (IFPushButton) not called.
The part that confuses me is how the print statements in the base class, Intefrace, are obviously being called as they are printing--but it still raises a RuntimeError saying that it hasn't been initialized, and of course fails to get so far as to create the app window. Most of the related messages I've found on stackoverflow are related to people initializing things incorrectly with the super() method--but I have quintuple-checked my super inits, and everything I see tells me it should be working, with the exception of what I linked above.
If I could understand more about why this is happening I'm hoping I can find a way to work around it. Any assistance is much appreciated--thanks in advance!
In the meantime I'm going to try to find how I might be unintentionally deepcopy-ing a C++ object...
EDIT: included the url in the link to other stack overflow post.
Adding a super call to the Interface class constructor is required:
def __init__(self, parent, name, title_txt=None, qt_obj=None, update_log_method=None):
super(Interface, self).__init__(parent)
...
Also, you're calling self.show(), where you probably mean qmw.show().

PyQt Code Splitting - Design Vs Functionality

Am struggling to comprehend how to split code in (Py)Qt. The aim is to have the design & navigation tabs in QMainWindow, each tab triggering code in other files. So far it only launches with the ActionClass in the same document / putting in an external document causes 'app not defined' when clicking the tab. The below works without errors, but is clunky.
class Main(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.u = Ui_MainWindow()
self.u.setupUi(self)
self.u.tabs.currentChanged.connect(self.TabsChanged)
def TabsChanged(self, i):
if i == self.u.tabs.indexOf(self.u.tabFirst): ActionClass.__init__
class ActionClass(Main):
def __init__(self):
app.u.lineEdit.setText("test")
app = Main()
app.show()
sys.exit(app.exec_())
The examples I keep seeing have all code in one document. Is there another way to do this e.g. where the ActionClass is in another file/writing u.lineEdit.setText instead of app.u.lineEdit.setText. It seems inheritance & an instance of Main can't be accessed from the ActionClasses doc, so I can't see how they would communicate back to the Main?
Much appreciated
As suggest #M4rtini you can separate your code into python modules. And then import them (use them) in your main module.
For instance the code you posted can be separated in to files:
# actions_class.py
class ActionClass(Main):
def __init__(self):
app.u.lineEdit.setText("test")
and
# main.py
from action_class import ActionClass # This line no need much explanation ;)
class Main(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.u = Ui_MainWindow()
self.u.setupUi(self)
self.u.tabs.currentChanged.connect(self.TabsChanged)
def TabsChanged(self, i):
if i == self.u.tabs.indexOf(self.u.tabFirst): ActionClass.__init__
app = Main()
app.show()
sys.exit(app.exec_())
In order to understand how import works see the link I left you above.
More explanation
Lest's see:
The correct way of executin code inside a __init__ method is creating an instance. See the example below.
class A:
def __init__(self):
print("Executing A.__init__")
print("Doing things wrong")
A.__init__ # This don't print enything
print("Doing things well")
A() # This works as expected.
So, you line reads:
if i == self.u.tabs.indexOf(self.u.tabFirst): ActionClass.__init__
and should reads:
if i == self.u.tabs.indexOf(self.u.tabFirst): ActionClass()
On the other hand, is a bad practice put code that's not for initialize the instance inside the __init__ methods.
If you don't need the instance but yet you want to store the functions inside a class (something like a c++ namespace) you creating
use #staticmethod decorator.
class A:
#staticmethod
def foo():
print("Oh, wow, a static method in Python!")
A.foo()
So, your ActionClass could be rewritten as:
class ActionClass(Main):
#staticmethod
def do_action:
app.u.lineEdit.setText("test")
ans then you can use it like this:
if i == self.u.tabs.indexOf(self.u.tabFirst): ActionClass.do_action()

How do I shut down PyQt's QtApplication correctly?

I don't know the first thing about Qt, but I'm trying to be cheeky and borrow code from elsewhere (http://lateral.netmanagers.com.ar/weblog/posts/BB901.html#disqus_thread). ;)
I have a problem. When I run test() the first time, everything works swimmingly. However, when I run it the second time, I get nasty segfaults. I suspect that the problem is that I'm not ending the qt stuff correctly. What should I change about this program to make it work multiple times? Thanks in advance!
from PyQt4 import QtCore, QtGui, QtWebKit
import logging
logging.basicConfig(level=logging.DEBUG)
class Capturer(object):
"""A class to capture webpages as images"""
def __init__(self, url, filename, app):
self.url = url
self.app = app
self.filename = filename
self.saw_initial_layout = False
self.saw_document_complete = False
def loadFinishedSlot(self):
self.saw_document_complete = True
if self.saw_initial_layout and self.saw_document_complete:
self.doCapture()
def initialLayoutSlot(self):
self.saw_initial_layout = True
if self.saw_initial_layout and self.saw_document_complete:
self.doCapture()
def capture(self):
"""Captures url as an image to the file specified"""
self.wb = QtWebKit.QWebPage()
self.wb.mainFrame().setScrollBarPolicy(
QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff)
self.wb.mainFrame().setScrollBarPolicy(
QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff)
self.wb.loadFinished.connect(self.loadFinishedSlot)
self.wb.mainFrame().initialLayoutCompleted.connect(
self.initialLayoutSlot)
logging.debug("Load %s", self.url)
self.wb.mainFrame().load(QtCore.QUrl(self.url))
def doCapture(self):
logging.debug("Beginning capture")
self.wb.setViewportSize(self.wb.mainFrame().contentsSize())
img = QtGui.QImage(self.wb.viewportSize(), QtGui.QImage.Format_ARGB32)
painter = QtGui.QPainter(img)
self.wb.mainFrame().render(painter)
painter.end()
img.save(self.filename)
self.app.quit()
def test():
"""Run a simple capture"""
app = QtGui.QApplication([])
c = Capturer("http://www.google.com", "google.png", app)
c.capture()
logging.debug("About to run exec_")
app.exec_()
DEBUG:root:Load http://www.google.com
QObject::connect: Cannot connect (null)::configurationAdded(QNetworkConfiguration) to QNetworkConfigurationManager::configurationAdded(QNetworkConfiguration)
QObject::connect: Cannot connect (null)::configurationRemoved(QNetworkConfiguration) to QNetworkConfigurationManager::configurationRemoved(QNetworkConfiguration)
QObject::connect: Cannot connect (null)::configurationUpdateComplete() to QNetworkConfigurationManager::updateCompleted()
QObject::connect: Cannot connect (null)::onlineStateChanged(bool) to QNetworkConfigurationManager::onlineStateChanged(bool)
QObject::connect: Cannot connect (null)::configurationChanged(QNetworkConfiguration) to QNetworkConfigurationManager::configurationChanged(QNetworkConfiguration)
Process Python segmentation fault (this last line is comes from emacs)
You need to handle the QApplication outside of the test functions, sort of like a singleton (it's actually appropriate here).
What you can do is to check if QtCore.qApp is something (or if QApplication.instance() returns None or something else) and only then create your qApp, otherwise, use the global one.
It will not be destroyed after your test() function since PyQt stores the app somewhere.
If you want to be sure it's handled correctly, just setup a lazily initialized singleton for it.
A QApplication should only be initialized once!
It can be used by as many Capture instances as you like, but you should start them in the mainloop.
See: https://doc.qt.io/qt-4.8/qapplication.html
You could also try "del app" after "app.exec_", but I am unsure about the results.
(Your original code runs fine on my system)
I would use urllib instead of webkit:
import urllib
class Capturer:
def capture(self, s_url, s_filename):
s_file_out, httpmessage = urllib.urlretrieve(s_url, s_filename, self.report)
def report(self, i_count, i_chunk, i_size):
print('retrived %5d of %5d bytes' % (i_count * i_chunk, i_size))
def test():
c = Capturer()
c.capture("http://www.google.com/google.png", "google1.png")
c.capture("http://www.google.com/google.png", "google2.png")
if __name__ == '__main__':
test()

Categories