2010-11-19 13 views
7

Próbuję uzyskać dane wyjściowe z procesu wieloprocesorowego Pythona wyświetlanego w guziku Tkinter.Jak wysłać dane wyjściowe procesora wieloprocesorowego Pythona do guika Tkinter

mogę wysłać wyjście z procesów poprzez GUI do powłoki poleceń, na przykład uruchamiając fllowing drobny skrypt w linii poleceń powłoki:

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

Mogę również wysłać wyjście z rodzica Do pole tekstowe, na przykład poprzez modyfikację powyżej zakomentowałeś płukanie stdout w myfunc

# sys.stdout.flush() 

i dodając natychmiast po „b3.grid ...” wiersz następujący:

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 

Jednak nie mogę wymyślić, jak wysłać dane wyjściowe z procesów do pola tekstowego. Czy brakuje mi czegoś prostego?

Odpowiedz

8

Można przekierować stdout/stderr do StringIO w myfunc(), a następnie wyślij cokolwiek do tego StringIO z powrotem do rodzica (zgodnie z sugestią unutbu). Zobacz moją odpowiedź na this question dla jednego sposobu wykonania tego przekierowania.

Od tego przykładem robi nieco więcej niż trzeba, tutaj jest wersja, która jest bardziej dostosowane do swoich określonych celów:

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

zwykłe ostrzeżenia dotyczące bezpieczeństwa stosuje się tutaj (czyli nie rób tego, chyba że można ufać nadawcy tych fragmentów kodu, aby nie robić nic głupiego/złośliwego).

Należy również pamiętać, że można to znacznie uprościć, jeśli nie ma potrzeby interpretowania dowolnej kombinacji wyrażeń w pythonach: i. Jeśli potrzebujesz tylko wywołać funkcję najwyższego poziomu, która generuje jakieś wyjścia, coś takiego może być bardziej odpowiednie:

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

Można przekazywać (wybierać) dane między procesami za pomocą multiprocessing.Pipe. Na przykład:

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

Zobacz Doug Hellman's tutorial na multiprocessing aby uzyskać więcej informacji.

+0

Unutbu - Dziękuję za bardzo pomocną odpowiedź. To na pewno rozwiązuje mój przykład zabawki. (W prawdziwej aplikacji procesy będą uruchamiać różne funkcje i generować komunikaty wyjściowe spontanicznie (tj. Nie drukować jakiegoś określonego tekstu nadanego im jako parametr) .Dziękuję się, czy udało się znaleźć "taniego", aby uniknąć dostania się do rur, ale myślę, że to prawdopodobnie jest droga ... – tchaz

0

Zakładając, że myfunc nazywa z wyjściem procesu, wystarczy napisać myfunc jak:

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

gdzie textwidget jest uchwyt do widgetu tekstowego

+0

Dzięki za sugestię, z pewnością pomogłoby to w moim przykładzie zabawkowym, ale może trochę uprościłem to, czego potrzebuję - w prawdziwej aplikacji procesy będą uruchamiać różne funkcje i generować komunikaty wyjściowe spontanicznie (tj. nie drukowanie określonego tekstu jako parametru). – tchaz

Powiązane problemy