2010-11-19 23 views
7

Sto cercando di ottenere l'output da un processo di multiprocessing python visualizzato in un gui di Tkinter.Come posso inviare l'output del processo di multiprocessing di Python a un Tkinter gui

posso inviare l'output da processi tramite una GUI per una shell di comando, ad esempio eseguendo il piccolo script di fllowing al prompt della shell:

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

posso anche inviare in uscita dal genitore ad un casella di testo, ad esempio modificando il sopra commentando il lavaggio di stdout in myfunc

# sys.stdout.flush() 

e aggiungendo subito dopo il "b3.grid ..." linea la seguente:

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 

Tuttavia non riesco a capire come inviare l'output dai processi alla casella di testo. Mi sto perdendo qualcosa di semplice?

risposta

8

Si potrebbe reindirizzare output/error a un StringIO in myfunc(), quindi invia ciò che viene scritto in quel StringIO al genitore (come suggerito da unutbu). Vedere la mia risposta a this question per un modo di fare questo reindirizzamento.

Dal momento che ad esempio fa un po 'più del necessario, ecco una versione che è più in linea con i vostri obiettivi dichiarati:

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

I soliti avvertimenti sulla sicurezza si applicano qui (vale a dire, non farlo a meno che non può fidarsi del mittente di questi snippet di codice per non fare nulla di stupido/malizioso).

Si noti inoltre che è possibile semplificarlo molto se non è necessario interpretare un mix arbitrario di espressioni python e. Se hai solo bisogno di chiamare una funzione di primo livello che genera alcune uscite, qualcosa di simile potrebbe essere più appropriato:

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

È possibile passare dati (selezionabili) tra processi utilizzando multiprocessing.Pipe. Per esempio:

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

Vedi Doug Hellman's tutorial su multiprocessing per ulteriori informazioni.

+0

Unutbu - Grazie per la risposta molto utile. Certamente risolve il mio esempio di giocattolo. (Nell'applicazione reale i processi eseguiranno funzioni diverse e genereranno messaggi di output spontaneamente (cioè non stamperanno alcun testo specifico dato loro come parametro). Mi chiedevo se fosse possibile trovare un "cheapo" per evitare di entrare nei tubi, ma immagino che probabilmente sia la strada da percorrere – tchaz

0

Supponendo che myfunc si chiama con l'uscita del processo, basta scrivere myfunc come:

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

dove textwidget è un handle al widget di testo

+0

Grazie per il suggerimento, sarebbe sicuramente d'aiuto con il mio esempio di giocattolo, ma forse ho semplificato un po 'quello che ho bisogno di un po' - nella vera applicazione i processi eseguiranno diverse funzioni e produrranno messaggi di output spontaneamente (cioè non stampa un testo specifico dato loro come parametro). – tchaz

Problemi correlati