Следуя подсказке GM , я сделал версию, которая, как мне кажется, подчиняется правилам Qt.Я создал класс ThreadLogger(logging.Handler)
, который я установил для обработки всех журналов в потоке Worker
и отправки их в основной поток через слоты и сигналы.
Я продолжал получать ошибку TypeError: emit() takes 2 positional arguments but 3 were given
, когда яунаследовал QtCore.QObject
(и logging.Handler
) в ThreadLogger
, что, как я подозреваю, было связано с тем, что я переопределял QtCore.Signal.emit (). Поэтому я поместил Сигнал в отдельный класс MyLog(QObject)
и просто использовал экземпляр этогов ThreadLogger
class MyLog(QtCore.QObject):
# create a new Signal
# - have to be a static element
# - class has to inherit from QObject to be able to emit signals
signal = QtCore.Signal(str)
# not sure if it's necessary to implement this
def __init__(self):
super().__init__()
А вот класс ThreadLogger(logging.Handler)
.Это просто генерирует все журналы через signal
в MyLog
выше
# custom logging handler that can run in separate thread, and emit all logs
# via signals/slots so they can be used to update the GUI in the main thread
class ThreadLogger(logging.Handler):
def __init__(self):
super().__init__()
self.log = MyLog()
# logging.Handler.emit() is intended to be implemented by subclasses
def emit(self, record):
msg = self.format(record)
self.log.signal.emit(msg)
Полный код:
import sys
import logging
import numpy as np
from PySide2 import QtWidgets, QtCore
def longFunction(logger):
logger.info("Start long running function")
i = 0
while True:
for j in range(10000):
t = np.arange(256)
sp = np.fft.fft(np.sin(t))
freq = np.fft.fftfreq(t.shape[-1])
sp = sp + freq
logger.info("%d" % i)
i += 1
# since I don't really need an event thread, I subclass QThread, as per
# https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.html
class Worker(QtCore.QThread):
def __init__(self, parent=None):
super().__init__(parent)
## set up logging
# __init__ is run in the thread that creates this thread, not in the
# new thread. But logging is thread-safe, so I don't think it matters
# create logger for this class
self.logger = logging.getLogger("Worker")
# set up log handler
self.logHandler = ThreadLogger()
self.logHandler.setFormatter(
logging.Formatter('%(asctime)s - %(levelname)s - %(threadName)s - %(message)s'))
self.logger.addHandler(self.logHandler)
# set the logging level
self.logger.setLevel(logging.DEBUG)
def run(self):
longFunction(self.logger)
class MyLog(QtCore.QObject):
# create a new Signal
# - have to be a static element
# - class has to inherit from QObject to be able to emit signals
signal = QtCore.Signal(str)
# not sure if it's necessary to implement this
def __init__(self):
super().__init__()
# custom logging handler that can run in separate thread, and emit all logs
# via signals/slots so they can be used to update the GUI in the main thread
class ThreadLogger(logging.Handler):
def __init__(self):
super().__init__()
self.log = MyLog()
# logging.Handler.emit() is intended to be implemented by subclasses
def emit(self, record):
msg = self.format(record)
self.log.signal.emit(msg)
class MyWidget(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
# read-only text box
self.logTextBox = QtWidgets.QPlainTextEdit(self)
self.logTextBox.setReadOnly(True)
self.resize(400, 500)
# start button
self.startButton = QtWidgets.QPushButton(self)
self.startButton.setText('Start')
# connect start button
self.startButton.clicked.connect(self.start)
# set up layout
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.logTextBox)
layout.addWidget(self.startButton)
self.setLayout(layout)
def start(self):
self.thread = Worker()
self.thread.finished.connect(self.threadFinished)
self.thread.start()
# we don't want to enable user to start another thread while this one
# is running so we disable the start button.
self.startButton.setEnabled(False)
# connect logger
self.thread.logHandler.log.signal.connect(self.write_log)
def threadFinished(self):
self.startButton.setEnabled(True)
# define a new Slot, that receives a string
@QtCore.Slot(str)
def write_log(self, log_text):
self.logTextBox.appendPlainText(log_text)
self.logTextBox.centerCursor() # scroll to the bottom
app = QtWidgets.QApplication(sys.argv)
w = MyWidget()
w.show()
app.exec_()
Я пока не совсем уверен, почему, но я получаю все журналы отlongFunction
в терминале, а также в окне GUI (но с разными форматами).Я также не на 100%, если это на самом деле потокобезопасно, и подчиняется всем правилам потоков Qt, но по крайней мере он не падает так же, как раньше.
Я оставлю этот ответ напару дней, и примите это тогда, если я не получу более качественных ответов, или окажется, что мое решение неверно!