2010-11-19 9 views
7

Estoy tratando de obtener una salida de un proceso de multiproceso de python que se muestra en una interfaz gráfica de usuario de Tkinter.¿Cómo puedo enviar la salida del proceso de multiprocesamiento de Python a una interfaz gráfica de usuario de Tkinter?

puedo enviar la salida de los procesos a través de una interfaz gráfica de usuario a una consola de comandos, por ejemplo mediante la ejecución de la pequeña secuencia de comandos fllowing en el intérprete de comandos:

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() 

También puedo enviar la salida de la padres a una cuadro de texto, por ejemplo, modificando la anterior comentando la enrojecimiento de la salida estándar en myfunc

# sys.stdout.flush() 

y añadiendo inmediatamente después de la "b3.grid ..." línea de lo siguiente:

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 

Sin embargo, no puedo encontrar la forma de enviar los resultados desde los Procesos al cuadro de texto. ¿Me estoy perdiendo algo simple?

Respuesta

8

puede redirigir stdout/stderr un StringIO en myfunc(), a continuación, envíe lo que se escribe en ese StringIO de vuelta al padre (como lo sugiere unutbu). Vea mi respuesta al this question para una forma de hacer esta redirección.

Desde ese ejemplo lo hace un poco más de lo necesario, aquí está una versión que está más alineado con sus objetivos declarados:

#!/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() 

Las advertencias habituales acerca de la seguridad se aplican aquí (es decir, no hacerlo a menos que puede confiar en el remitente de estos fragmentos de código para no hacer nada estúpido/malicioso).

También tenga en cuenta que puede simplificar esto mucho si no necesita interpretar una combinación arbitraria de expresiones de pitón y declaraciones. Si solo necesita llamar a una función de nivel superior que genera algunas salidas, algo como esto puede ser más apropiado:

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() 
3

Puede pasar datos (seleccionables) entre procesos utilizando un multiprocessing.Pipe. Por ejemplo:

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() 

Ver Doug Hellman's tutorial en multiprocessing para más información.

+0

Unutbu - Gracias por la respuesta tan útil. Ciertamente resuelve mi ejemplo de juguete. (En la aplicación real, los procesos ejecutarán diferentes funciones y producirán mensajes de salida de forma espontánea (es decir, no imprimiendo algún texto específico que se les dé como parámetro). Me he preguntado si era posible encontrar un 'cheapo' para evitar meterme en las tuberías, pero supongo que ese es el camino a seguir. – tchaz

0

Suponiendo que myfunc se llama con la salida del proceso, acaba de escribir myfunc como:

def myfunc(text):  
    textwidget.insert("end", text) 

donde textwidget es un identificador para el control de texto

+0

Gracias por la sugerencia. Sin duda ayudaría con mi ejemplo de juguete. Sin embargo, tal vez simplifiqué demasiado lo que necesito un poco: en la aplicación real los procesos ejecutarán diferentes funciones y producirán mensajes de salida espontáneamente (es decir, no imprimiendo algún texto específico que se les haya dado como parámetro). – tchaz

Cuestiones relacionadas