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
Convert Columns to String in Pandas
Convert a List with Strings All to Lowercase or Uppercase
On Localhost, How to Pick a Free Port Number
Conda Command Is Not Recognized on Windows 10
Why Does Defining _Getitem_ on a Class Make It Iterable in Python
Download Image with Selenium Python
Python Extending with - Using Super() Python 3 VS Python 2
Why am I Getting Importerror: No Module Named Pip ' Right After Installing Pip
How to Write a File or Data to an S3 Object Using Boto3
Salt and Hash a Password in Python
Conditionally Fill Column Values Based on Another Columns Value in Pandas
Importing from a Relative Path in Python
Selenium Element Not Visible Exception
Getting a MAChine's External Ip Address with Python
Function with Varying Number of for Loops (Python)