Wie man nicht-blockierend/real-time-Verhalten von Python-logging-Modul? (Ausgabe PyQt QTextBrowser)
Beschreibung: ich habe geschrieben ein benutzerdefiniertes Protokoll-handler für die capturing-Protokoll-Ereignisse und schreiben Sie einen QTextBrowser Objekt (Arbeits-Beispielcode unten).
Problem: Drücken Sie die Taste ruft someProcess()
. Dies schreibt zwei strings an die logger
Objekt. Allerdings werden die strings nur angezeigt, nachdem someProcess()
gibt.
Frage: Wie bekomme ich den angemeldet strings erscheinen, um in die QTextBrowser Objekt sofort/in Echtzeit? (d.h., sobald ein logger
output-Methode wird aufgerufen)
from PyQt4 import QtCore, QtGui
import sys
import time
import logging
logger = logging.getLogger(__name__)
class ConsoleWindowLogHandler(logging.Handler):
def __init__(self, textBox):
super(ConsoleWindowLogHandler, self).__init__()
self.textBox = textBox
def emit(self, logRecord):
self.textBox.append(str(logRecord.getMessage()))
def someProcess():
logger.error("line1")
time.sleep(5)
logger.error("line2")
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = QtGui.QWidget()
textBox = QtGui.QTextBrowser()
button = QtGui.QPushButton()
button.clicked.connect(someProcess)
vertLayout = QtGui.QVBoxLayout()
vertLayout.addWidget(textBox)
vertLayout.addWidget(button)
window.setLayout(vertLayout)
window.show()
consoleHandler = ConsoleWindowLogHandler(textBox)
logger.addHandler(consoleHandler)
sys.exit(app.exec_())
BEARBEITEN: Dank der Antwort von @abarnert, ich es geschafft, dieses Stück zu schreiben zu arbeiten, code mit QThread. Ich Unterklassen QThread
um einige Funktion someProcess
in einem hintergrund-thread. Für die Signalisierung, hatte ich Rückgriff auf die old-style-Signals und Slots (ich bin mir nicht sicher, wie es in der new-style). Erstellt habe ich eine dummy QObject, um in der Lage zu emittieren Signale, die von der logging-handler.
from PyQt4 import QtCore, QtGui
import sys
import time
import logging
logger = logging.getLogger(__name__)
#------------------------------------------------------------------------------
class ConsoleWindowLogHandler(logging.Handler):
def __init__(self, sigEmitter):
super(ConsoleWindowLogHandler, self).__init__()
self.sigEmitter = sigEmitter
def emit(self, logRecord):
message = str(logRecord.getMessage())
self.sigEmitter.emit(QtCore.SIGNAL("logMsg(QString)"), message)
#------------------------------------------------------------------------------
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
# Layout
textBox = QtGui.QTextBrowser()
self.button = QtGui.QPushButton()
vertLayout = QtGui.QVBoxLayout()
vertLayout.addWidget(textBox)
vertLayout.addWidget(self.button)
self.setLayout(vertLayout)
# Connect button
self.button.clicked.connect(self.buttonPressed)
# Thread
self.bee = Worker(self.someProcess, ())
self.bee.finished.connect(self.restoreUi)
self.bee.terminated.connect(self.restoreUi)
# Console handler
dummyEmitter = QtCore.QObject()
self.connect(dummyEmitter, QtCore.SIGNAL("logMsg(QString)"),
textBox.append)
consoleHandler = ConsoleWindowLogHandler(dummyEmitter)
logger.addHandler(consoleHandler)
def buttonPressed(self):
self.button.setEnabled(False)
self.bee.start()
def someProcess(self):
logger.error("starting")
for i in xrange(10):
logger.error("line%d" % i)
time.sleep(2)
def restoreUi(self):
self.button.setEnabled(True)
#------------------------------------------------------------------------------
class Worker(QtCore.QThread):
def __init__(self, func, args):
super(Worker, self).__init__()
self.func = func
self.args = args
def run(self):
self.func(*self.args)
#------------------------------------------------------------------------------
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
- Wenn das edit war eine richtige Antwort, es wäre die kanonische Lösung für das gemeinsame problem der Anzeige von log-Meldungen in beiden PyQt und PySide-widgets. Im Gegensatz zu ähnlichen Lösungen, die für alle ähnliche Fragen haben (z.B., dieser, this)Ihre Bearbeiten, nutzt die slots und Signale und damit arbeitet nicht blockingly. Das ist fantastisch.
- Was weniger toll ist die Verwendung von alten Stil slots und Signale und ein hypetextual
QTextBrowser
eher als einen nur-lese-KlartextQTextArea
. Gebrauch von new-style slots und Signale sollten vermeiden die Notwendigkeit für diedummyEmitter
Vermittler. Ebenso, Zitat offizielleQTextBrowser
Dokumentation: "Wenn Sie wollen, einen text-browser ohne hypertext-navigation verwendenQTextEdit
und verwendenQTextEdit::setReadOnly()
zum deaktivieren der Bearbeitung."
Du musst angemeldet sein, um einen Kommentar abzugeben.
Hier das eigentliche problem ist, dass Sie die Blockierung der gesamten GUI für 5 Sekunden vom Schlaf-in den Haupt-thread. Das kannst du nicht tun, oder keine updates erscheinen, wird der Benutzer nicht in der Lage sein, um die Interaktion mit Ihrer app, etc. Das logging-Problem ist nur ein kleines sub-Folge, dass große problem.
Und wenn Ihr echtes Programm ist, aufrufen von code von einer Drittanbieter-Modul, das dauert 5 Sekunden, oder tut etwas blockieren, wird es genau das gleiche problem.
Im Allgemeinen, es gibt zwei Möglichkeiten, das zu tun langsam, blockieren Dinge, ohne zu blockieren eine GUI (oder andere event-loop-basierte) app:
Tun der Arbeit in einem hintergrund-thread. Je nach GUI-framework, aus einem hintergrund-thread, Sie können in der Regel nicht die Funktionen aufrufen, die direkt auf die GUI, oder ändern Sie die Objekte, die Sie stattdessen verwenden Sie einen Mechanismus, um Nachrichten zu posten, um die Ereignis-Schleife. In Qt, die Sie in der Regel tun dies durch das signal-slot-Mechanismus. Sehen diese Frage für details.
Brechen den job in non-blocking oder garantiert-nur-sehr-kurzfristig-blockiert Arbeitsplätze, schnell zurück, jede Planung, die nächste rechts vor der Rückkehr. (Mit einigen GUI-frameworks, die Sie tun können, den Gegenwert in-line " durch den Aufruf etwas wie
safeYield
oder der Aufruf der event-Schleife rekursiv, aber Sie tun das nicht mit Qt.)Gegeben, dass
someProcess
ist einigen externen code, die Sie nicht ändern können, die entweder von Sekunden zu beenden, oder tut etwas blockieren, können Sie nicht verwenden Sie option 2. Also, option 1: führen Sie es in einem hintergrund-thread.Glücklicherweise ist dies einfach. Qt hat Möglichkeiten, dies zu tun, aber Python ' s Wege sind auch einfacher:
Nun, Sie brauchen, um zu ändern
ConsoleWindowLogHandler.emit
so, dass, statt direkt änderntextBox
sendet es ein signal zu bekommen, dass getan in den Haupt-thread. Sehen Threads und QObjects für alle details, und einige gute Beispiele.Konkret: Die Mandelbrot-Beispiel verwendet eine
RenderThread
dass tatsächlich nicht alles zeichnen, sondern sendet stattdessen einerenderedImage
signal; dieMandelbrotWidget
dann eineupdatePixmap
slot, verbindet es sich mit derrenderedImage
signal. In der gleichen Weise, Ihre log-handler nicht wirklich update-text-box, sondern senden Sie einegotLogMessage
signal; dann hätten Sie eineLogTextWidget
mit einemupdateLog
Steckplatz verbindet auf dieses signal. Natürlich auch für Ihre einfachen Fall, Sie können halten Sie zusammen in einer einzigen Klasse, nur so lange, wie Sie eine Verbindung zwischen beiden Seiten eine signal-slot-Verbindung, anstatt einen direkten Aufruf der Methode.Werden Sie wahrscheinlich wollen, um entweder zu halten
t
irgendwo Rum undjoin
es beim Herunterfahren oder einstellent.daemon = True
.Entweder Weg, wenn Sie wissen wollen, Wann
someProcess
geschehen ist, müssen Sie andere Mittel der Kommunikation zurück zu Ihrem Haupt-thread, wenn es fertig ist—wieder, mit Qt, ist die übliche Antwort ist, um ein signal zu senden. Und diese können Sie auch bekommen ein Ergebnis zurück aussomeProcess
. Und Sie müssen Sie nicht ändernsomeProcess
um dies zu tun, definieren eine wrapper-Funktion, die AnrufesomeProcess
Signale und deren Ergebnis, und rufen Sie die wrapper-Funktion aus dem hintergrund-thread.someProcess()
fordert ein Python-Modul, ruft die logger liefern status-updates, und ich möchte in der Lage, diese anzeigen status-updates in Echtzeit in einen QTextBrowser. Ich weißsubprocess
lässt mich erfassenstdout
in einer nicht blockierenden Weise, aber in diesem Fall möchte ich erfassen die Protokollierung von Ereignissen in einer nicht blockierenden Weise. Das Python-Modul nicht über die GUI.subprocess
(oderQProcess
wenn Sie es vorziehen) von einem hintergrund-thread ist nicht schwieriger als sonst etwas zu tun, von einem hintergrund-thread. Bin ich hier etwas fehlt? Haben Sie Lesen Sie Threads und QObjects? Erstellen von hintergrund-threads ist einfach, und ausgeben von Signalen aus, die Sie automatisch funktioniert über threads, und das ist die Art und Weise Sie befassen sich mit blockieren in Qt.subProcess
ist einfach zu bedienen, aber es hat zum Aufruf eines externen Programm-ich bin mir nicht sicher, wie ich es mit einem Python-Modul (die kann ich nicht ändern), außerdem bin ich nicht sicher, wie das logging-Modul kommunizieren würden, wenn der Teilprozess ist extern.QProcess
scheint ähnlich zu sein.subprocess
weil Sie getan haben, und ich dachte, vielleicht war es inhärent zu Ihrer Lösung, die Sie ausgeführt wurden, jemand anderes Skript in einem untergeordneten Prozess. Wenn Sie nicht möchten, zu verwenden, die ein thread ist alles was Sie brauchen. Ich habe aktualisiert die Antwort zu erklären.Bauen auf @Gilead code und @Cecil ' s Vorschläge, update ich den code durch ändern der alt-Stil, neuer Stil-signal/slot und die änderung der
QTextBrowser
zuQTextEdit
.Hier ist eine weitere Methode. In diesem Beispiel füge ich ein
StreamHandler
zu dem logger, schreibt in einen Puffer, durch Erben von beidenQObject
undStringIO
: Wenn die Prozedur auf ein nicht-leerer string, derbufferMessage
signal und erfasst in deron_bufferMessage
slot.Die beste Sache über diese Methode ist, dass Sie können log-Meldungen aus Module/threads von Euch Haupt-app, ohne dass jede Referenz auf den logger, z.B. durch den Aufruf
logging.log(logging.INFO, logging_message)
oderlogging.info(logging_message)
logger
. Aber Dank deinem Beispiel habe ich jetzt sehen, wie man schreibt new-style-signal und slots. Danke!logging.info
stattlogger.info
ausmyThread
... können Sie auch direkt die Ausgabe in einem bestimmten logger durch den Aufruflogging.getLogger('someLogger')
logging
nichtaddHandler
Methode. Ich bin mir nicht sicher, ich verstehe (aber wohlgemerkt, dies ist der erste Zeit bin ich mit demlogging
- Modul, daher kann ich nicht verstehen, die Nuancen!)