【问题标题】:How can I send python multiprocessing Process output to a Tkinter gui如何将 python 多处理进程输出发送到 Tkinter gui
【发布时间】:2010-11-19 17:42:10
【问题描述】:

我正在尝试从 Tkinter gui 中显示的 python 多处理进程获取输出。

我可以通过 gui 将进程的输出发送到命令 shell,例如通过在 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()

我也可以将输出从 parent 发送到文本框,例如通过在 myfunc 中注释掉 stdout 的刷新来修改上述内容

#    sys.stdout.flush()

并在“b3.grid...”行之后立即添加以下内容:

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

但是我不知道如何将进程的输出发送到文本框。我错过了一些简单的东西吗?

【问题讨论】:

    标签: python user-interface tkinter stdout multiprocessing


    【解决方案1】:

    您可以将 stdout/stderr 重定向到 myfunc() 中的 StringIO,然后将写入该 StringIO 的任何内容发送回父级(如 unutbu 建议的那样)。请参阅我对 this question 的回答,了解执行此重定向的一种方法。

    由于该示例的功能超出了您的需要,因此以下版本更符合您的既定目标:

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

    关于安全性的常见警告适用于此处(即,除非您可以信任这些代码 sn-ps 的发送者不会做任何愚蠢/恶意的事情,否则不要这样做)。

    另外请注意,如果您不需要解释 Python 表达式 语句的任意组合,则可以大大简化此操作。如果你只需要调用一个生成一些输出的顶级函数,这样的事情可能更合适:

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

    【讨论】:

      【解决方案2】:

      您可以使用multiprocessing.Pipe 在进程之间传递(可提取的)数据。 例如:

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

      有关更多信息,请参阅 multiprocessing 上的 Doug Hellman's tutorial

      【讨论】:

      • Unutbu - 感谢您提供的非常有帮助的答案。它当然解决了我的玩具示例。 (在实际应用程序中,进程将运行不同的功能并自发产生输出消息(即不打印作为参数提供给它们的某些特定文本)。我想知道是否有可能找到一个“便宜”来避免进入管道,但我想这可能是要走的路。
      【解决方案3】:

      假设myfunc是用进程的输出调用的,只需要这样写myfunc:

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

      其中 textwidget 是文本小部件的句柄

      【讨论】:

      • 感谢您的建议。这肯定会对我的玩具示例有所帮助。然而,我可能过于简化了我需要的东西 - 在实际应用程序中,进程将运行不同的功能并自发产生输出消息(即不打印作为参数提供给它们的某些特定文本)。
      猜你喜欢
      • 2010-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-05
      • 1970-01-01
      • 2019-08-15
      • 2021-08-27
      相关资源
      最近更新 更多