Python multiprocessing redirect stdout of a child process to a Tkinter Text
multiprocessing, python, tkinter
Solution
It is still possible to use queues without having to get rid of all of your `print` statements. You can use a `Process` dependent `stdout` redirect to do this. The solution below uses a `Queue` subclass to mimic `stdout`. That queue is then monitored by a thread that looks for new text that gets pumped into the text widget.
import sys
import time
from multiprocessing import Process
from multiprocessing.queues import Queue
from threading import Thread
from Tkinter import *
# This function takes the text widget and a queue as inputs.
# It functions by waiting on new data entering the queue, when it
# finds new data it will insert it into the text widget
def text_catcher(text_widget,queue):
while True:
text_widget.insert(END, queue.get())
# This is a Queue that behaves like stdout
class StdoutQueue(Queue):
def __init__(self,*args,**kwargs):
Queue.__init__(self,*args,**kwargs)
def write(self,msg):
self.put(msg)
def flush(self):
sys.__stdout__.flush()
def test_child(q):
# This line only redirects stdout inside the current process
sys.stdout = q
# or sys.stdout = sys.__stdout__ if you want to print the child to the terminal
print 'child running'
def test_parent(q):
# Again this only redirects inside the current (main) process
# commenting this like out will cause only the child to write to the widget
sys.stdout = q
print 'parent running'
time.sleep(0.5)
Process(target=test_child,args=(q,)).start()
if __name__ == '__main__':
gui_root = Tk()
gui_txt = Text(gui_root)
gui_txt.pack()
q = StdoutQueue()
gui_btn = Button(gui_root, text='Test', command=lambda:test_parent(q),)
gui_btn.pack()
# Instantiate and start the text monitor
monitor = Thread(target=text_catcher,args=(gui_txt,q))
monitor.daemon = True
monitor.start()
gui_root.mainloop()
Problem
I'm trying to use Tkinter GUI to launch a child process and display it stdout/stderr output to a Text widget. Initially, I thought the sys.stdout can be easily redirected to the Text widget by setting "sys.stdout = text_widget" but seems not. It comes to an error: "Text instance has no attribute 'flush'". I checked online and got some solutions, like using a Queue to communicate with the child process. However, none of them fit my case because of my special requirement: - the child process should better be launched by "multiprocessing.Process" because it would be easier to use shared variables, which makes subprocess solutions usable. - the codes of child process are already there with quite a lot "print" inside, so I don't want to modify them to something like "Queue.put()" or else. In this case, could anyone come to a solution of getting a "multiprocessing.Process"'s "print" output and display to a Tkinter Text? Many thanks! An example code of my case is a follows: ``` import sys import time from multiprocessing import Process from Tkinter import * def test_child(): print 'child running' def test_parent(): print 'parent running' time.sleep(0.5) Process(target=test_child).start() def set_txt(msg): gui_txt.insert(END, str(msg)) gui_txt.see(END) if __name__ == '__main__': gui_root = Tk() gui_txt = Text(gui_root) gui_txt.pack() gui_btn = Button(gui_root, text='Test', command=test_parent) gui_btn.pack() gui_txt.write = set_txt sys.stdout = gui_txt gui_root.mainloop() ```