PyQt: No error msg (traceback) on exit - python

My PyQt application no longer prints the error (stderr?) to the console.
I use QtDesigner and import the UI like this:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from PyQt5.uic import loadUiType
Ui_MainWindow, QMainWindow = loadUiType("test.ui")
class Main(QMainWindow, Ui_MainWindow):
"""Main window"""
def __init__(self,parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.testfunc)
def testfunc(self):
print(9/0)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
test.ui contains a QPushButton and a label. When I call testfunc (which obviously gives an error) in a non-Qt application, I get the error message, traceback, etc. When I execute this code, it just exits.
I wrote a PyQt application without QtDesigner before and it printed the errors to the console as expected. What's the difference with QtDesigner and inheritance?

This is probably due to changes in the way exceptions are dealt with in PyQt-5.5. To quote from the PyQt5 Docs:
In PyQt v5.5 an unhandled Python exception will result in a call to
Qt’s qFatal() function. By default this will call abort() and the
application will terminate. Note that an application installed
exception hook will still take precedence.
When I run your example in a normal console, this is what I see:
$ python test.py
Traceback (most recent call last):
File "test.py", line 213, in testfunc
print(9/0)
ZeroDivisionError: division by zero
Aborted (core dumped)
So the main difference is that the application will now immediately abort when encountering an unhandled exception (i.e. just like a normal python script would). Of course, you can still control this behaviour by using a try/except block or globally by overriding sys.excepthook.
If you're not seeing any traceback, this may be due to an issue with the Python IDE you're using to run your application.
PS:
As a bare minimum, the old PyQt4 behaviour of simply printing the traceback to stdout/stderr can be restored like this:
def except_hook(cls, exception, traceback):
sys.__excepthook__(cls, exception, traceback)
if __name__ == "__main__":
import sys
sys.excepthook = except_hook

I've been using python's traceback module in conjunction with a try/except statement to make sure the traceback is printed before exiting:
https://docs.python.org/3/library/traceback.html
Spefically, I use traceback.print_exc()

Related

QEventLoop in QAxWidget instance failed

I'm working on terminal based PyQt application using Windows OCX api.
And I'm having trouble over implement(calling exec) QEventLoop in QAxWidget instance.
If I call exec() in main QApplication main loop, everthing is fine.
But, calling exec() in instance of QAxWidget which is instantiated in main loop, it does not work as expected.
When exec() called(_on_event_connect in code), error message showed up like
"QEventLoop:exec: instance 0x18479d8 has already called exec()"
And on the moment of calling exit(), QEventLoop seems not running.
And the application is hang after this.
The below is simplified example of my code, It tries call QAxWidget method "dynamicCall" as soon as log-in process finished.
It consists of pyQt signal/slot mechanism, get event from API server.
Please understand that you can not execute this code because of it needs specific APIs installation and registered user-id.
But I hope you can see the points of problem.
main.py
import sys
from PyQt5.QtWidgets import QApplication
from api import OpenApi
class Main():
def __init__(self):
self.api = OpenApi()
self.api.comm_connect()
# self.api.req_basic_stock_info("035420") -> if I call here, works well
if __name__ == "__main__":
app = QApplication(sys.argv)
main = Main()
sys.exit(app.exec_())
api.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
class OpenApi(QAxWidget):
def __init__(self):
super().__init__()
self.setControl("KHOPENAPI.KHOpenAPICtrl.1")
self.OnEventConnect.connect(self._on_event_connect)
self.OnReceiveTrData.connect(self._on_receive_tr_data)
self._qloop = QEventLoop()
def loop(self):
if not self._qloop.isRunning():
logger.info(f"-->exec")
self._qloop.exec()
else:
logger.info("exec skipped")
def unloop(self):
if self._qloop.isRunning():
logger.info("-->exit")
self._qloop.exit()
else:
logger.info(f"exit skipped")
def comm_connect(self):
self.dynamicCall("CommConnect()")
self.loop()
def comm_rq_data(self, rqname, trcode, next, screen_no):
self.dynamicCall("CommRqData(QString, QString, int, QString)", rqname, trcode, next, screen_no)
self.loop()
def _on_event_connect(self, err_code):
if err_code == 0:
logger.info("logged in")
else:
logger.info("log-in failed")
self.unloop()
# If I call this function here, QEventLoop fails
self.req_basic_stock_info("000660")
def _on_receive_tr_data(self, screen_no, rqname, trcode, record_name, next,
unused1, unused2, unused3, unused4):
logger.info(f"OnReceivTrData: {rqname}")
self.unloop()
def req_basic_stock_info(self, code):
# I want to call this function inside instance on certain condition.
self.set_input_value("item", code)
scrno = "2000"
self.comm_rq_data("ITEM_INFO_REQ", "opt10080", "0", scrno)
And output is like below.
12:42:53,54 test INFO -->exec
12:42:58,961 test INFO logged in
12:42:58,962 test INFO -->exit
12:42:58,977 test INFO -->exec
QEventLoop::exec: instance 0x35c34a0 has already called exec()
12:42:59,33 test INFO OnReceivTrData:ITEM_INFO_REQ
12:42:59,35 test INFO exit skipped
As eyllanesc told me, I changed functions related assign/deassign QEventLoop like below. And it works.
def loop(self):
self._qloop = QEventLoop()
self._qloop.exec()
def unloop(self):
try:
self._qloop.exit()
except AttributeError as e:
logger.error(e)
update
I'm afraid this issue has not been solved yet. It was successful when req_basic_stock_info function is called on _on_event_connect event, But I call it at another event function, it hang again without any error message. There is no difference in calling mechanism.
Calling the exit() function does not imply that the QEventLoop can be reused, therefore the error message is signaled. In this case it is better to use a new QEventLoop:
def unloop(self):
if self._qloop.isRunning():
logger.info("-->exit")
self._qloop.exit()
self._qloop = QEventLoop()
else:
logger.info(f"exit skipped")

Exception handled surprisingly in Pyside slots

Problem: When exceptions are raised in slots, invoked by signals, they do not seem to propagate as usual through Pythons call stack. In the example code below invoking:
on_raise_without_signal(): Will handle the exception as expected.
on_raise_with_signal(): Will print the exception and then unexpectedly print the success message from the else block.
Question: What is the reason behind the exception being handled surprisingly when raised in a slot? Is it some implementation detail/limitation of the PySide Qt wrapping of signals/slots? Is there something to read about in the docs?
PS: I initially came across that topic when I got surprising results upon using try/except/else/finally when implementing a QAbstractTableModels virtual methods insertRows() and removeRows().
# -*- coding: utf-8 -*-
"""Testing exception handling in PySide slots."""
from __future__ import unicode_literals, print_function, division
import logging
import sys
from PySide import QtCore
from PySide import QtGui
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
class ExceptionTestWidget(QtGui.QWidget):
raise_exception = QtCore.Signal()
def __init__(self, *args, **kwargs):
super(ExceptionTestWidget, self).__init__(*args, **kwargs)
self.raise_exception.connect(self.slot_raise_exception)
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
# button to invoke handler that handles raised exception as expected
btn_raise_without_signal = QtGui.QPushButton("Raise without signal")
btn_raise_without_signal.clicked.connect(self.on_raise_without_signal)
layout.addWidget(btn_raise_without_signal)
# button to invoke handler that handles raised exception via signal unexpectedly
btn_raise_with_signal = QtGui.QPushButton("Raise with signal")
btn_raise_with_signal.clicked.connect(self.on_raise_with_signal)
layout.addWidget(btn_raise_with_signal)
def slot_raise_exception(self):
raise ValueError("ValueError on purpose")
def on_raise_without_signal(self):
"""Call function that raises exception directly."""
try:
self.slot_raise_exception()
except ValueError as exception_instance:
logger.error("{}".format(exception_instance))
else:
logger.info("on_raise_without_signal() executed successfully")
def on_raise_with_signal(self):
"""Call slot that raises exception via signal."""
try:
self.raise_exception.emit()
except ValueError as exception_instance:
logger.error("{}".format(exception_instance))
else:
logger.info("on_raise_with_signal() executed successfully")
if (__name__ == "__main__"):
application = QtGui.QApplication(sys.argv)
widget = ExceptionTestWidget()
widget.show()
sys.exit(application.exec_())
As you've already noted in your question, the real issue here is the treatment of unhandled exceptions raised in python code executed from C++. So this is not only about signals: it also affects reimplemented virtual methods as well.
In PySide, PyQt4, and all PyQt5 versions up to 5.5, the default behaviour is to automatically catch the error on the C++ side and dump a traceback to stderr. Normally, a python script would also automatically terminate after this. But that is not what happens here. Instead, the PySide/PyQt script just carries on regardless, and many people quite rightly regard this as a bug (or at least a misfeature). In PyQt-5.5, this behaviour has now been changed so that qFatal() is also called on the C++ side, and the program will abort like a normal python script would. (I don't know what the current situation is with PySide2, though).
So - what should be done about all this? The best solution for all versions of PySide and PyQt is to install an exception hook - because it will always take precedence over the default behaviour (whatever that may be). Any unhandled exception raised by a signal, virtual method or other python code will firstly invoke sys.excepthook, allowing you to fully customise the behaviour in whatever way you like.
In your example script, this could simply mean adding something like this:
def excepthook(cls, exception, traceback):
print('calling excepthook...')
logger.error("{}".format(exception))
sys.excepthook = excepthook
and now the exception raised by on_raise_with_signal can be handled in the same way as all other unhandled exceptions.
Of course, this does imply that best practice for most PySide/PyQt applications is to use largely centralised exception handling. This often includes showing some kind of crash-dialog where the user can report unexpected errors.
According to the Qt5 docs you need to handle exceptions within the slot being invoked.
Throwing an exception from a slot invoked by Qt's signal-slot connection mechanism is considered undefined behaviour, unless it is handled within the slot
State state;
StateListener stateListener;
// OK; the exception is handled before it leaves the slot.
QObject::connect(&state, SIGNAL(stateChanged()), &stateListener, SLOT(throwHandledException()));
// Undefined behaviour; upon invocation of the slot, the exception will be propagated to the
// point of emission, unwinding the stack of the Qt code (which is not guaranteed to be exception safe).
QObject::connect(&state, SIGNAL(stateChanged()), &stateListener, SLOT(throwUnhandledException()));
If the slot was invoked directly, like a regular function call,
exceptions may be used. This is because the connection mechanism is
bypassed when invoking slots directly
In the first case you call slot_raise_exception() directly, so this is fine.
In the second case you are invoking it via the raise_exception signal, so the exception will only propagate up to the point where slot_raise_exception() is called. You need to place the try/except/else inside slot_raise_exception() for the exception to be handled correctly.
Thanks for answering guys. I found ekhumoros answer particularly useful to understand where the exceptions are handled and because of the idea to utilize sys.excepthook.
I mocked up a quick solution via context manager to temporarily extend the current sys.excepthook to record any exception in the realm of "C++ calling Python" (as it seems to happen when slots are invoked by signals or virtual methods) and possibly re-raise upon exiting the context to achieve expected control flow in try/except/else/finally blocks.
The context manager allows on_raise_with_signal to maintain the same control flow as on_raise_without_signal with the surrounding try/except/else/finally block.
# -*- coding: utf-8 -*-
"""Testing exception handling in PySide slots."""
from __future__ import unicode_literals, print_function, division
import logging
import sys
from functools import wraps
from PySide import QtCore
from PySide import QtGui
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
class ExceptionHook(object):
def extend_exception_hook(self, exception_hook):
"""Decorate sys.excepthook to store a record on the context manager
instance that might be used upon leaving the context.
"""
#wraps(exception_hook)
def wrapped_exception_hook(exc_type, exc_val, exc_tb):
self.exc_val = exc_val
return exception_hook(exc_type, exc_val, exc_tb)
return wrapped_exception_hook
def __enter__(self):
"""Temporary extend current exception hook."""
self.current_exception_hook = sys.excepthook
sys.excepthook = self.extend_exception_hook(sys.excepthook)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Reset current exception hook and re-raise in Python call stack after
we have left the realm of `C++ calling Python`.
"""
sys.excepthook = self.current_exception_hook
try:
exception_type = type(self.exc_val)
except AttributeError:
pass
else:
msg = "{}".format(self.exc_val)
raise exception_type(msg)
class ExceptionTestWidget(QtGui.QWidget):
raise_exception = QtCore.Signal()
def __init__(self, *args, **kwargs):
super(ExceptionTestWidget, self).__init__(*args, **kwargs)
self.raise_exception.connect(self.slot_raise_exception)
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
# button to invoke handler that handles raised exception as expected
btn_raise_without_signal = QtGui.QPushButton("Raise without signal")
btn_raise_without_signal.clicked.connect(self.on_raise_without_signal)
layout.addWidget(btn_raise_without_signal)
# button to invoke handler that handles raised exception via signal unexpectedly
btn_raise_with_signal = QtGui.QPushButton("Raise with signal")
btn_raise_with_signal.clicked.connect(self.on_raise_with_signal)
layout.addWidget(btn_raise_with_signal)
def slot_raise_exception(self):
raise ValueError("ValueError on purpose")
def on_raise_without_signal(self):
"""Call function that raises exception directly."""
try:
self.slot_raise_exception()
except ValueError as exception_instance:
logger.error("{}".format(exception_instance))
else:
logger.info("on_raise_without_signal() executed successfully")
def on_raise_with_signal(self):
"""Call slot that raises exception via signal."""
try:
with ExceptionHook() as exception_hook:
self.raise_exception.emit()
except ValueError as exception_instance:
logger.error("{}".format(exception_instance))
else:
logger.info("on_raise_with_signal() executed successfully")
if (__name__ == "__main__"):
application = QtGui.QApplication(sys.argv)
widget = ExceptionTestWidget()
widget.show()
sys.exit(application.exec_())
This way of handling expcetions is not a surprise taking into consideration that the Signal/Slot architecture proposes a loosely coupled intercation between the signals and the slots. This means that the signal should not be expecting anything to happen inside the slots.
Although timmwagener's solution was pretty clever, it should be used with caution. Probably the problem is not with how Exceptions are handled between Qt Connections, but that the signal/slot architecture is not ideal for your application. Also, that solution would not work if a slot from a different thread is connected, or a Qt.QueuedConnection is used.
A good way of tackling the problem of errors raised in slots is to determine that at the connection and not the emitting. Then the erros can be handled in a loosely coupled way.
class ExceptionTestWidget(QtGui.QWidget):
error = QtCore.Signal(object)
def abort_execution():
pass
def error_handler(self, err):
self.error.emit(error)
self.abort_execution()
(...)
def connect_with_async_error_handler(sig, slot, error_handler, *args,
conn_type=None, **kwargs):
#functools.wraps(slot)
def slot_with_error_handler(*args):
try:
slot(*args)
except Exception as err:
error_handler(err)
if conn_type is not None:
sig.connect(slot_with_error_handler, conn_type)
else:
sig.connect(slot_with_error_handler)
This way, we would be complying to the requirements in the Qt5 docs, stating that you need to handle exceptions within the slot being invoked.
Throwing an exception from a slot invoked by Qt's signal-slot
connection mechanism is considered undefined behaviour, unless it is
handled within the slot
PS: This is just a sugestion based on a very small overview of your use case. There is no right/wrong way of solving this, I just wanted to bring out a different point of view : )

Saving exceptions in Tkinter using Python

I have developed several Python programs for others that use Tkinter to receive input from the user. In order to keep things simple and user-friendly, the command line or python console are never brought up (ie. .pyw files are used), so I'm looking into using the logging library to write error text to a file when an exception occurs. However, I'm having difficulty getting it to actually catch the exceptions. For example:
We write a function that will cause an error:
def cause_an_error():
a = 3/0
Now we try to log the error normally:
import logging
logging.basicConfig(filename='errors.log', level=logging.ERROR)
try:
cause_an_error()
except:
logging.exception('simple-exception')
As expected, the program errors, and logging writes the error to errors.log. Nothing appears in the console. However, there is a different result when we implement a Tkinter interface, like so:
import logging
import Tkinter
logging.basicConfig(filename='errors.log', level=logging.ERROR)
try:
root = Tkinter.Tk()
Tkinter.Button(root, text='Test', command=cause_an_error).pack()
root.mainloop()
except:
logging.exception('simple-exception')
In this case, pressing the button in the Tkinter window causes the error. However, this time, nothing is written to the file, and the following error appears in the console:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1536, in __call__
return self.func(*args)
File "C:/Users/samk/Documents/GitHub/sandbox/sandbox2.pyw", line 8, in cause_an_error
a = 3/0
ZeroDivisionError: integer division or modulo by zero
Is there a different way to catch and log this error?
It's not very well documented, but tkinter calls a method for exceptions that happen as the result of a callback. You can write your own method to do whatever you want by setting the attribute report_callback_exception on the root window.
For example:
import tkinter as tk
def handle_exception(exception, value, traceback):
print("Caught exception:", exception)
def raise_error():
raise Exception("Sad trombone!")
root = tk.Tk()
# setup custom exception handling
root.report_callback_exception=handle_exception
# create button that causes exception
b = tk.Button(root, text="Generate Exception", command=raise_error)
b.pack(padx=20, pady=20)
root.mainloop()
For reference:
http://epydoc.sourceforge.net/stdlib/Tkinter.Tk-class.html#report_callback_exception
Since this question was about logging and being explicit where the error is occurring, I will expand on Bryan (totally correct) answer.
First, you have to import some additional useful modules.
import logging
import functools # for the logging decorator
import traceback # for printing the traceback to the log
Then, you have to configure the logger and the logging function:
logging.basicConfig(filename='/full/path/to_your/app.log',
filemode='w',
level=logging.INFO,
format='%(levelname)s: %(message)s')
def log(func):
# logging decorator
#functools.wraps(func)
def wrapper_log(*args, **kwargs):
msg = ' '.join(map(str, args))
level = kwargs.get('level', logging.INFO)
if level == logging.INFO:
logging.info(msg)
elif level == logging.ERROR:
logging.exception(msg)
traceback.print_exc()
return wrapper_log
#log
def print_to_log(s):
pass
Note that the function doing the logging is actually log and you can use it for both printing errors and regular info into your log file. How you use it: with the function print_to_log decorated with log. And instead of pass you can put some regular print(), so that you save the message to the log and print it to the console. You can replace pass with whatever commands you prefer.
Note nb. 2 we use the traceback in the log function to track where exactly your code generated an error.
To handle the exception, you do as in the already accepted answer. The addition is that you pass the message (i.e. the traceback) to the print_to_log function:
def handle_exception(exc_type, exc_value, exc_traceback):
# prints traceback in the log
message = ''.join(traceback.format_exception(exc_type,
exc_value,
exc_traceback))
print_to_log(message)
Having configured all this, now you you tell your Tkinter app that it has to use the handle_exception function:
class App(tk.Tk):
# [the rest of your app code]
if __name__ == '__main__':
app = App()
app.report_callback_exception = handle_exception
app.mainloop()
Or alternatively, if you work with multiple classes, you can even instruct the class itself to use the andle_exception function:
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.report_callback_exception = handle_exception
# [the rest of your app code]
if __name__ == '__main__':
app = App()
app.mainloop()
In this way, your app code looks lean, you don't need any try/except method, and you can log at info level whichever event in your app.

How to catch a KeyboardInterrupt raised in the IPython console in a Tkinter app?

I have a simple Tkinter window, started from an IPython console (so mainloop() is not called, in case that's relevant)
When I press Ctrl+C in the console, nothing happens... until the Tkinter window does something, at which point it catches the KeyboardInterrupt and raises an error.
The question is: how do I catch that KeyboardInterrupt? It seems that no matter where I put a try: block, it is too late already...
Here's an example:
class App(object):
def __init__(self,):
self.root = tk.Tk()
self.root.bind('<FocusIn>', self.focus_in)
def focus_in(self, event):
print 'I got focus'
in the IPython console:
app = App()
Now press Ctrl+C in the console. Then click on the App window... and bang:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/lib-tk/Tkinter.py", line 1481, in __call__
def __call__(self, *args):
KeyboardInterrupt
Adding a 'try/except KeyboardInterrupt' block inside focus_in does not help.
So? Is this a bug? Should TKinter even see that interrupt? Can I disable SIGINT while IPython is idle? (Naturally I still want it when it is running some code...)
One way could be to create a mainloop in another thread that's always waiting for the KeyboardInterrupt exception, although I don't know if this is a good aproach but I believe It will fix your problem.

PyQt crashing out as a Windows APPCRASH

I have a very short PyQt program (n.b. that is a PythonFiddle link - this seems to crash horribly in Firefox, so code is also posted below) which prints output to a QTextEdit (using code from this SO answer). When I run the code (on Windows), it results in an APPCRASH. Some observations:
if I add a time.sleep call (i.e. uncomment out line 53), then the program completes fine
if I don’t redirect the output to the QEdit (i.e. comment out line 34) then it works regardless of whether the time.sleep call is commented out or not
I assume that this implies that the code redirecting the stdout is broken somehow - but I'm struggling to understand what's wrong with it to result in this behaviour - any pointers gratefully received!
Full error message
Problem signature:
Problem Event Name: APPCRASH
Application Name: pythonw.exe
Application Version: 0.0.0.0
Application Timestamp: 5193f3be
Fault Module Name: QtGui4.dll
Fault Module Version: 4.8.5.0
Fault Module Timestamp: 52133a81
Exception Code: c00000fd
Exception Offset: 00000000005cbdb7
OS Version: 6.1.7601.2.1.0.256.48
Locale ID: 2057
Additional Information 1: 5c9c
Additional Information 2: 5c9c27bb85eb40149b414993f172d16f
Additional Information 3: bc7e
Additional Information 4: bc7e721eaea1ec56417325adaec101aa
Pythonfiddle crashes horribly on Firefox (for me at least), so code below too:
import os, sys, time, calendar, math
from PyQt4 import QtCore, QtGui
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text): self.textWritten.emit(str(text))
class myWrapper(QtGui.QMainWindow):
def __init__(self):
super(myWrapper, self).__init__()
self.toolbar = self.addToolBar("MainMenu")
self.toolbar.addAction(QtGui.QAction("myProg", self, triggered=self.myProgActions))
def myProgActions(self): self.setCentralWidget(myWidget())
class myWidget(QtGui.QWidget):
def __init__(self):
super(myWidget, self).__init__()
self.myBtn = QtGui.QPushButton('Run!', self)
self.myBtn.clicked.connect(self.startTest)
self.outputViewer = QtGui.QTextEdit()
self.grid = QtGui.QGridLayout()
self.grid.addWidget(self.myBtn)
self.grid.addWidget(self.outputViewer)
self.setLayout(self.grid)
def startTest(self):
self.myLongTask = TaskThread()
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.myLongTask.start()
def normalOutputWritten(self, text):
cursor = self.outputViewer.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.outputViewer.setTextCursor(cursor)
self.outputViewer.ensureCursorVisible()
QtGui.qApp.processEvents()
class TaskThread(QtCore.QThread):
def __init__(self): super(TaskThread, self).__init__()
def run(self): myProgClass()
class myProgClass:
def __init__(self):
for i in range(0,100):
print "thread", i+1, " ", math.sqrt(i)
#time.sleep(0.005)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
myApp = myWrapper()
myApp.show()
sys.exit(app.exec_())
Ok, one thing first about the thread safety of your program. Because you are connecting a QObject to stdout, calls to print will interact with this QObject. But you should only interact with a QObject from the thread it was created in. The implementation you have is potentially thread unsafe if you call print from a thread other than the one the QObject resides in.
In your case, you are calling print from a QThread while the EmmittingStream(QObject) resides in the main thread. I suggest you thus change your code to make it threadsafe, like so:
self.myLongTask = TaskThread()
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
sys.stdout.moveToThread(self.myLongTask)
self.myLongTask.start()
Note that now calling print from the MainThread of your application will result in thread-unsafe behaviour. Since you aren't doing that at the moment, you are OK for now, but be warned!
I really suggest you read http://qt-project.org/doc/qt-4.8/threads-qobject.html and get your head around everything it talks about. Understanding that document is the key to avoiding annoying crashes due to threads.
--
Now, The issue with the crash is apparently caused by your call to processEvents(). I haven't worked out why (I imagine it is related to threads somehow...), but I can tell you that you do not need that line! Because you are using signals/slots, once the normalOutputWritten method runs, control returns to the Qt Event Loop anyway, and it will continue to process events as normal. There is thus no need to force Qt to process events!
Hope that helps!
EDIT: For an example of how to make your EmittingStream/print calls thread-safe, see here: Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread

Categories

Resources