python - Why don't the signals emit? -
the application
i'm trying build python shell pyqt5 application using stdlib interactiveconsole
can let users script live plots. i'm using qtextedit
display stdout shell.
the problem
when loops in shell, application freezes because insertplaintext()
qtextedit
fast. wrote buffer delay inserts few milliseconds. however, noticed ran blocking functions time.sleep()
in loops, freeze. prints inside loops displayed after loop done. not happen if buffer disabled.
for eg, if in shell:
>>>for in range(10): ... time.sleep(1) ... print(i) ...
this print after 10 seconds.
code
this minimal version write according mvce guidelines.
here main.ui
file:
<?xml version="1.0" encoding="utf-8"?> <ui version="4.0"> <class>main_window</class> <widget class="qmainwindow" name="main_window"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>600</height> </rect> </property> <property name="sizepolicy"> <sizepolicy hsizetype="preferred" vsizetype="preferred"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> <property name="windowtitle"> <string>mainwindow</string> </property> <property name="tabshape"> <enum>qtabwidget::rounded</enum> </property> <widget class="qwidget" name="central_widget"> <layout class="qhboxlayout" name="horizontallayout"> <item> <layout class="qvboxlayout" name="console_layout"> <item> <widget class="qtextedit" name="console_log"> <property name="undoredoenabled"> <bool>false</bool> </property> </widget> </item> <item> <layout class="qhboxlayout" name="horizontallayout_4"> <item> <widget class="qlabel" name="console_prompt"> <property name="text"> <string/> </property> </widget> </item> <item> <widget class="qlineedit" name="console_input"> <property name="frame"> <bool>true</bool> </property> </widget> </item> </layout> </item> </layout> </item> </layout> </widget> <widget class="qmenubar" name="menu_bar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>26</height> </rect> </property> </widget> <widget class="qstatusbar" name="status_bar"/> </widget> <resources/> <connections/> </ui>
here themain.py
file:
import sys code import interactiveconsole io import stringio queue import queue, empty pyqt5 import uic pyqt5.qtcore import pyqtslot, qthread, qobject, pyqtsignal, qtimer pyqt5.qtgui import qtextoption, qtextcursor pyqt5.qtwidgets import qapplication __author__ = "daegontaven" __copyright__ = "daegontaven" __license__ = "gpl3" class basesignals(qobject): """ standard set of pyqtsignals. """ signal_str = pyqtsignal(str) signal_int = pyqtsignal(int) signal_float = pyqtsignal(float) signal_list = pyqtsignal(list) signal_tuple = pyqtsignal(tuple) signal_dict = pyqtsignal(dict) signal_object = pyqtsignal(object) def __init__(self): qobject.__init__(self) class delayedbuffer(qobject): """ buffer uses queue store strings. removes first appended string first in constant interval. """ written = pyqtsignal(str) def __init__(self, output, delay): """ :param output: used access basesignals :param delay: delay emitting """ super().__init__() self.output = output # set delay self.delay = delay self.queue = queue() self.timer = qtimer() self.timer.timeout.connect(self.process) self.timer.start(self.delay) def write(self, string): self.queue.put(string) def process(self): """ try send data stream """ try: data = self.queue.get(block=false) self.written.emit(data) except empty: pass def emit(self, string): """ force emit of string. """ self.output.signal_str.emit(string) class consolestream(stringio): """ custom streamio class emits signal on each write. """ def __init__(self, enabled=true, *args, **kwargs): """ starts delayed buffer store writes due ui refresh limitations. :param enabled: set false bypass buffer """ stringio.__init__(self, *args, **kwargs) self.enabled = enabled self.output = basesignals() # buffer self.thread = qthread() self.buffer = delayedbuffer(self.output, delay=5) self.buffer.movetothread(self.thread) self.buffer.written.connect(self.get) self.thread.start() def write(self, string): """ overrides parent write method , emits signal meant received interpreters. :param string: single write output stdout """ if self.enabled: self.buffer.write(string) else: self.output.signal_str.emit(string) def get(self, string): self.output.signal_str.emit(string) class pythoninterpreter(qobject, interactiveconsole): """ reimplementation of builtin interactiveconsole work threads. """ output = pyqtsignal(str) push_command = pyqtsignal(str) multi_line = pyqtsignal(bool) def __init__(self): qobject.__init__(self) self.l = {} interactiveconsole.__init__(self, self.l) self.stream = consolestream() self.stream.output.signal_str.connect(self.console) self.push_command.connect(self.command) def write(self, string): self.output.emit(string) def runcode(self, code): """ overrides , captures stdout , stdin interactiveconsole. """ sys.stdout = self.stream sys.stderr = self.stream sys.excepthook = sys.__excepthook__ result = interactiveconsole.runcode(self, code) sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ return result @pyqtslot(str) def command(self, command): """ :param command: line retrieved console_input on returnpressed """ result = self.push(command) self.multi_line.emit(result) @pyqtslot(str) def console(self, string): """ :param string: processed output stream """ self.output.emit(string) class mainwindow: """ main gui window. opens maximized. """ def __init__(self): self.ui = uic.loadui("main.ui") self.ui.showmaximized() # console properties self.ui.console_log.document().setmaximumblockcount(1000) self.ui.console_log.setwordwrapmode(qtextoption.wrapanywhere) self.ps1 = '>>>' self.ps2 = '...' self.ui.console_prompt.settext(self.ps1) # spawn interpreter self.thread = qthread() self.thread.start() self.interpreter = pythoninterpreter() self.interpreter.movetothread(self.thread) # interpreter signals self.ui.console_input.returnpressed.connect(self.send_console_input) self.interpreter.output.connect(self.send_console_log) self.interpreter.multi_line.connect(self.prompt) def prompt(self, multi_line): """ sets prompt use. """ if multi_line: self.ui.console_prompt.settext(self.ps2) else: self.ui.console_prompt.settext(self.ps1) def send_console_input(self): """ send input grabbed qlineedit prompt console. """ command = self.ui.console_input.text() self.ui.console_input.clear() self.interpreter.push_command.emit(str(command)) def send_console_log(self, command): """ set output interactiveconsole in qtextedit. auto scroll scrollbar. """ # checks if scrolled old_cursor = self.ui.console_log.textcursor() old_scrollbar = self.ui.console_log.verticalscrollbar().value() new_scrollbar = self.ui.console_log.verticalscrollbar().maximum() if old_scrollbar == new_scrollbar: scrolled = true else: scrolled = false # sets text self.ui.console_log.insertplaintext(command) # scrolls/moves cursor based on available data if old_cursor.hasselection() or not scrolled: self.ui.console_log.settextcursor(old_cursor) self.ui.console_log.verticalscrollbar().setvalue(old_scrollbar) else: self.ui.console_log.movecursor(qtextcursor.end) self.ui.console_log.verticalscrollbar().setvalue( self.ui.console_log.verticalscrollbar().maximum() ) def main(): app = qapplication(sys.argv) window = mainwindow() sys.exit(app.exec_()) if __name__ == "__main__": main()
the class basesignals
needed communication between main thread , interpreter. here transcript why implemented.
what know
this line responsible inserting plain text self.output.signal_str.emit(data)
. emit()
happens inside qthread
. until multiple self.buffer.write()
finished emit()
won't processed. thought adding qapplication.processevents()
in delayedbuffer.process()
help. doesn't. admit wrong this.
any appreciated. in advance.
your interpreter thread blocking on interactiveconsole.runcode()
call. not able process signals until call completes. why see delayed output.
you can effect you're after changing
self.interpreter.output.connect(self.send_console_log)
to
self.interpreter.stream.output.signal_str.connect(self.send_console_log)
for old school debugging, disconnect you're stderr handling , sprinkle print statements around like...
print('runcode after', file=sys.stderr)
Comments
Post a Comment