Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I send python multiprocessing Process output to a Tkinter gui

I'm trying to get output from a python multiprocessing Process displayed in a Tkinter gui.

I can send output from Processes via a gui to a command shell, for example by running the fllowing tiny script at a shell prompt:

from multiprocessing import Process  
import sys  

def myfunc(text):    
    print text  
    sys.stdout.flush() 

def f1():  
    p1 = Process(target = myfunc, args = ("Surprise",))  
    p1.start()  

def f2():  
    p2 = Process(target = myfunc, args = ("Fear",))  
    p2.start()  

def fp():  
    myfunc("... and an almost fanatical devotion to the Pope")  

a = Tk()  

b1 = Button(a, text="Process 1", command=f1)  
b1.grid(row=0, column=0, pady=10, padx=10, sticky=SE)  
b2 = Button(a, text="Process 2", command=f2)  
b2.grid(row=0, column=1, pady=10, padx=10, sticky=SE)  
b3 = Button(a, text="Parent", command=fp)  
b3.grid(row=0, column=2, pady=10, padx=10, sticky=SE)  

if __name__ == "__main__":  
    a.mainloop()

I can also send output from the parent to a Text box, for example by modifying the above by commenting out the flushing of stdout in myfunc

#    sys.stdout.flush()

and adding immediately after the "b3.grid..." line the following:

class STDText(Text):
    def __init__(self, parent, cnf={}, **kw):
        Text.__init__(self, parent, cnf, **kw)
    def write(self, stuff):
        self.config(state=NORMAL)
        self.insert(END, stuff)
        self.yview_pickplace("end")
        self.config(state=DISABLED)

messages = STDText(a, height=2.5, width=30, bg="light cyan", state=DISABLED)   
messages.grid(row=1, column=0, columnspan=3)
sys.stdout = messages

However I can't figure out how to send output from the Processes to the text box. Am I missing something simple?

like image 240
tchaz Avatar asked Nov 19 '10 17:11

tchaz


2 Answers

You could redirect stdout/stderr to a StringIO in myfunc(), then send whatever gets written into that StringIO back to the parent (as suggested by unutbu). See my answer to this question for one way of doing this redirection.

Since that example does a bit more than you need, here's a version that's more aligned with your stated goals:

#!/usr/bin/env python
import sys
from cStringIO import StringIO
from code import InteractiveConsole
from contextlib import contextmanager
from multiprocessing import Process, Pipe

@contextmanager
def std_redirector(stdin=sys.stdin, stdout=sys.stdin, stderr=sys.stderr):
    tmp_fds = stdin, stdout, stderr
    orig_fds = sys.stdin, sys.stdout, sys.stderr
    sys.stdin, sys.stdout, sys.stderr = tmp_fds
    yield
    sys.stdin, sys.stdout, sys.stderr = orig_fds

class Interpreter(InteractiveConsole):
    def __init__(self, locals=None):
        InteractiveConsole.__init__(self, locals=locals)
        self.output = StringIO()
        self.output = StringIO()

    def push(self, command):
        self.output.reset()
        self.output.truncate()
        with std_redirector(stdout=self.output, stderr=self.output):
            try:
                more = InteractiveConsole.push(self, command)
                result = self.output.getvalue()
            except (SyntaxError, OverflowError):
                pass
            return more, result

def myfunc(conn, commands):
    output = StringIO()
    py = Interpreter()
    results = ""

    for line in commands.split('\n'):
        if line and len(line) > 0:
            more, result = py.push(line + '\n')
            if result and len(result) > 0:
                results += result

    conn.send(results)
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()

    commands = """
print "[42, None, 'hello']"

def greet(name, count):
    for i in range(count):
        print "Hello, " + name + "!"

greet("Beth Cooper", 5)
fugazi
print "Still going..."
"""
    p = Process(target=myfunc, args=(child_conn, commands))
    p.start()
    print parent_conn.recv()
    p.join()

The usual caveats about security apply here (i.e., don't do this unless you can trust the sender of these code snippets to not do anything stupid/malicious).

Also note that you can simplify this a lot if you don't need to interpret an arbitrary mix of python expressions and statements. If you only need to call a top-level function that generates some outputs, something like this may be more appropriate:

def dosomething():
    print "Doing something..."

def myfunc(conn, command):
    output = StringIO()
    result = ""
    with std_redirector(stdout=output, stderr=output):
        try:
            eval(command)
            result = output.getvalue()
        except Exception, err:
            result = repr(err)

    conn.send(result)
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    command = "dosomething()"
    p = Process(target=myfunc, args=(child_conn, command))
    p.start()
    print parent_conn.recv()
    p.join()
like image 103
evadeflow Avatar answered Sep 27 '22 17:09

evadeflow


You can pass (picklable) data between processes using a multiprocessing.Pipe. For example:

import Tkinter
import multiprocessing as mp

class STDText(Tkinter.Text):
    def __init__(self, parent, cnf={}, **kw):
        Tkinter.Text.__init__(self, parent, cnf, **kw)
    def write(self, stuff):
        self.config(state=Tkinter.NORMAL)
        self.insert(Tkinter.END, stuff)
        self.yview_pickplace("end")
        self.config(state=Tkinter.DISABLED)

def myfunc(conn,text):    
    conn.send(text)
    conn.close()

class Gui(object):
    def __init__(self):
        self.a=Tkinter.Tk()  
        b1=Tkinter.Button(self.a, text="Process 1", command=self.foo)  
        b1.grid(row=0, column=0, pady=10, padx=10, sticky=Tkinter.SE)  
        b2=Tkinter.Button(self.a, text="Process 2", command=self.bar)  
        b2.grid(row=0, column=1, pady=10, padx=10, sticky=Tkinter.SE)  
        b3=Tkinter.Button(self.a, text="Parent", command=self.baz)  
        b3.grid(row=0, column=2, pady=10, padx=10, sticky=Tkinter.SE)  
        self.messages=STDText(
            self.a, height=2.5, width=30, bg="light cyan", state=Tkinter.DISABLED)   
        self.messages.grid(row=1, column=0, columnspan=3)
        self.a.mainloop()        
    def call_myfunc(self,text):
        parent_conn, child_conn=mp.Pipe()
        proc=mp.Process(target=myfunc, args=(child_conn,text,))  
        proc.start()  
        self.messages.write(parent_conn.recv())
        proc.join()       
    def foo(self):
        self.call_myfunc('Foo\n')
    def bar(self):
        self.call_myfunc('Bar\n')        
    def baz(self):
        parent_conn, child_conn=mp.Pipe()
        myfunc(child_conn,'Baz\n')
        self.messages.write(parent_conn.recv())

if __name__ == "__main__":  
    Gui()

See Doug Hellman's tutorial on multiprocessing for more information.

like image 41
unutbu Avatar answered Sep 27 '22 15:09

unutbu