How to Capture Output of Python's Interpreter and Show in a Text Widget

How to capture output of Python's interpreter and show in a Text widget?

I assume that with "output from the interpreter", you mean output written to the console or terminal window, such as output produced with print().

All console output produced by Python gets written to the program's output streams sys.stdout (normal output) and sys.stderr (error output, such as exception tracebacks). These are file-like objects.

You can replace these streams with your own file-like object. All your custom implementation must provide is a write(text) function. By providing your own implementation, you can forward all output to your widget:

class MyStream(object):
def write(self, text):
# Add text to a QTextEdit...

sys.stdout = MyStream()
sys.stderr = MyStream()

If you ever need to reset these streams, they are still available as sys.__stdout__ and sys.__stderr__:

sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__

Update

Here is some working code for PyQt4. First define a stream that reports data written to it with a Qt signal:

from PyQt4 import QtCore

class EmittingStream(QtCore.QObject):

textWritten = QtCore.pyqtSignal(str)

def write(self, text):
self.textWritten.emit(str(text))

Now, in your GUI, install an instance of this stream to sys.stdout and connect the textWritten signal to a slot that writes the text to a QTextEdit:

# Within your main window class...

def __init__(self, parent=None, **kwargs):
# ...

# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)

def __del__(self):
# Restore sys.stdout
sys.stdout = sys.__stdout__

def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
# Maybe QTextEdit.append() works as well, but this is how I do it:
cursor = self.textEdit.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.textEdit.setTextCursor(cursor)
self.textEdit.ensureCursorVisible()

How do I direct console output to a pyqt5 plainTextEdit widget with Python?

I have a user interface, TableManagerWindow, that I've been maintaining and developing in Qt designer. After converting via pyuic to a *.py file, I was able to implement what Ferdinand Beyer had suggested in the link you provided above. Simple button to print text to terminal and it indeed does get appended to the QTextEdit widget via append(). Not sure this fits the bill for you for some reason, but I can vouch that it worked for me as well. I'm not savvy enough to get the nuance that is causing your issue, but figured I'd put this here just in case. Admins feel free to delete this if it's extraneous, but it works.

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

# Define a stream, custom class, that reports data written to it, with a Qt signal
class EmittingStream(QtCore.QObject):

textWritten = QtCore.pyqtSignal(str)

def write(self, text):
self.textWritten.emit(str(text))

class Ui_TableManagerWindow(object):
def setupUi(self, TableManagerWindow):
#define all of my widgets, layout, etc here
.
.
.
# Install a custom output stream by connecting sys.stdout to instance of EmmittingStream.
sys.stdout = EmittingStream(textWritten=self.output_terminal_written)

# Create my signal/connections for custom method
self.source_dir_button.clicked.connect(self.sourceDirButtonClicked)

self.retranslateUi(TableManagerWindow)
QtCore.QMetaObject.connectSlotsByName(TableManagerWindow)

def retranslateUi(self, TableManagerWindow):
.
.
.

#custom method that prints to output terminal. The point is to have this emmitted out to my QTextEdit widget.
def sourceDirButtonClicked(self):
for i in range(10):
print("The Source DIR button has been clicked " + str(i) + " times")

#custom method to write anything printed out to console/terminal to my QTextEdit widget via append function.
def output_terminal_written(self, text):
self.output_terminal_textEdit.append(text)

if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
TableManagerWindow = QtWidgets.QMainWindow()
ui = Ui_TableManagerWindow()
ui.setupUi(TableManagerWindow)
TableManagerWindow.show()
sys.exit(app.exec_())

stdout prints to PyQT text widget after function ends

I found solution which using QThread. Described here

For me it's working (but i don't know if it's the best) because in real case function is calling after pressing button from another module and i don't want change it somehow.

btw thanks @furas for idea of app.processEvents(). It's answering question, but not working in all cases. Especially with my code which i put in other question related with same app

Output Console (print function) to Gui TextArea

The print function writes over sys.stdout so the solution is to assign some QObject that has a write method that emits a signal. For this you can use contextlib.redirect_stdout:

import os
import sys
from contextlib import redirect_stdout
from functools import cached_property
from pathlib import Path

from PySide2.QtCore import (
QCoreApplication,
QDateTime,
QObject,
Qt,
QTimer,
QUrl,
Signal,
)
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine

CURRENT_DIRECTORY = Path(__file__).resolve().parent

class RedirectHelper(QObject):
stream_changed = Signal(str, name="streamChanged", arguments=["stream"])

def write(self, message):
self.stream_changed.emit(message)

class TimerTest(QObject):
@cached_property
def timer(self):
return QTimer(interval=1000, timeout=self.handle_timeout)

def handle_timeout(self):
print(QDateTime.currentDateTime().toString())

def start(self):
self.timer.start()

def main():
ret = 0
redirect_helper = RedirectHelper()
with redirect_stdout(redirect_helper):

app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()

engine.rootContext().setContextProperty("redirectHelper", redirect_helper)
filename = os.fspath(CURRENT_DIRECTORY / "main.qml")
url = QUrl.fromLocalFile(filename)

def handle_object_created(obj, obj_url):
if obj is None and url == obj_url:
QCoreApplication.exit(-1)

engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
engine.load(url)

timer_test = TimerTest()
timer_test.start()

ret = app.exec_()

sys.exit(ret)

if __name__ == "__main__":
main()
import QtQuick 2.12
import QtQuick.Controls 2.12

ApplicationWindow {
id: root

width: 640
height: 480
visible: true

Flickable {
id: flickable

flickableDirection: Flickable.VerticalFlick
anchors.fill: parent

TextArea.flickable: TextArea {
id: textArea

anchors.fill: parent
readOnly: true
font.pointSize: 8
color: "#f9930b"
wrapMode: TextEdit.Wrap
placeholderTextColor: "#f9930b"
opacity: 1
placeholderText: qsTr("Console")

background: Rectangle {
radius: 12
border.width: 2
border.color: "#f9930b"
}

}

ScrollBar.vertical: ScrollBar {
}

}

Connections {
function onStreamChanged(stream) {
textArea.insert(textArea.length, stream);
}

target: redirectHelper
}

}

PyQt: displaying Python interpreter output

Aaron made a very good point, but my problem had a much simpler answer than the intricacies of object orientation in python...

sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) 

needs to be after any print statements - print statements before this will be directed to the standard stdout, i.e. the interpreter console.

How can I write output from a Subprocess to a text widget in tkinter in realtime?

welcome to stack overflow. I modified your code a bit (you had a , after defining start_button and didnt import sys, also i put your code below ##### Window Setting #### into a boilerplate-code).

Your main issue was, that you do not make your Text widget available in your run and furthermore in your test function (executed as a thread). So i handed over your widget as an argument to both functions (probably not the most pythonic way, however). For executing a command bound to a button i used from functools import partial and binded the command including an argument via command=partial(run, textbox). Then i simply handed over the argument in run to the thread with args=[textbox] in the line where you create & start the thread. Finally, i updated the textbox with textbox.insert(tk.END, msg + "\n") in your test function while removing the print(). The insert appends any text at the end to the textbox, the "\n" starts a new line.

Here is the (slightly restructured) complete code (app.py):

import tkinter as tk
import subprocess
import threading
import sys
from functools import partial

# ### classes ####

class Redirect:

def __init__(self, widget, autoscroll=True):
self.widget = widget
self.autoscroll = autoscroll

def write(self, textbox):
self.widget.insert('end', textbox)
if self.autoscroll:
self.widget.see('end') # autoscroll

def flush(self):
pass

def run(textbox=None):
threading.Thread(target=test, args=[textbox]).start()

def test(textbox=None):

p = subprocess.Popen("python myprogram.py".split(), stdout=subprocess.PIPE, bufsize=1, text=True)
while p.poll() is None:
msg = p.stdout.readline().strip() # read a line from the process output
if msg:
textbox.insert(tk.END, msg + "\n")

if __name__ == "__main__":
fenster = tk.Tk()
fenster.title("My Program")
textbox = tk.Text(fenster)
textbox.grid()
scrollbar = tk.Scrollbar(fenster, orient=tk.VERTICAL)
scrollbar.grid()

textbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=textbox.yview)

start_button = tk.Button(fenster, text="Start", command=partial(run, textbox))
start_button.grid()

old_stdout = sys.stdout
sys.stdout = Redirect(textbox)

fenster.mainloop()
sys.stdout = old_stdout

And here is the code of the test-file myprogram.py i created:

import time

for i in range(10):
print(f"TEST{i}")
time.sleep(1)


Related Topics



Leave a reply



Submit