【问题标题】:Python multiprocessing redirect stdout of a child process to a Tkinter TextPython 多处理将子进程的标准输出重定向到 Tkinter 文本
【发布时间】:2014-05-30 04:53:03
【问题描述】:

我正在尝试使用 Tkinter GUI 来启动子进程并将其 stdout/stderr 输出显示到 Text 小部件。最初,我认为 sys.stdout 可以通过设置“sys.stdout = text_widget”轻松重定向到文本小部件,但似乎不是。报错:“Text instance has no attribute 'flush'”。

我在网上查了一下,得到了一些解决方案,比如使用队列与子进程通信。但是,由于我的特殊要求,它们都不适合我的情况:

  1. 子进程最好由“multiprocessing.Process”启动,因为它更容易使用共享变量,这使得子进程解决方案可用。
  2. 子进程的代码已经存在,里面有很多“打印”,所以我不想将它们修改为“Queue.put()”之类的东西。

在这种情况下,任何人都可以找到获得“multiprocessing.Process”的“打印”输出并显示到 Tkinter Text 的解决方案吗?非常感谢!

我的案例的示例代码如下:

import sys
import time
from multiprocessing import Process
from Tkinter import *

def test_child():
    print 'child running'

def test_parent():
    print 'parent running'
    time.sleep(0.5)
    Process(target=test_child).start()

def set_txt(msg):
    gui_txt.insert(END, str(msg))
    gui_txt.see(END)

if __name__ == '__main__':
    gui_root = Tk()
    gui_txt = Text(gui_root)
    gui_txt.pack()
    gui_btn = Button(gui_root, text='Test', command=test_parent)
    gui_btn.pack()

    gui_txt.write = set_txt
    sys.stdout = gui_txt

    gui_root.mainloop()

【问题讨论】:

    标签: python tkinter multiprocessing


    【解决方案1】:

    仍然可以使用队列,而不必删除所有 print 语句。您可以使用 Process 依赖 stdout 重定向来执行此操作。下面的解决方案使用Queue 子类来模仿stdout。然后,该队列由一个线程监视,该线程会查找被泵入文本小部件的新文本。

    import sys
    import time
    from multiprocessing import Process
    from multiprocessing.queues import Queue
    from threading import Thread
    from Tkinter import *
    
    # This function takes the text widget and a queue as inputs.
    # It functions by waiting on new data entering the queue, when it 
    # finds new data it will insert it into the text widget 
    def text_catcher(text_widget,queue):
        while True:
            text_widget.insert(END, queue.get())
    
    # This is a Queue that behaves like stdout
    class StdoutQueue(Queue):
        def __init__(self,*args,**kwargs):
            Queue.__init__(self,*args,**kwargs)
    
        def write(self,msg):
            self.put(msg)
    
        def flush(self):
            sys.__stdout__.flush()
    
    
    def test_child(q):
        # This line only redirects stdout inside the current process 
        sys.stdout = q
        # or sys.stdout = sys.__stdout__ if you want to print the child to the terminal
        print 'child running'
    
    def test_parent(q):
        # Again this only redirects inside the current (main) process
        # commenting this like out will cause only the child to write to the widget 
        sys.stdout = q                                                                                                                                                                                                                                                         
        print 'parent running'
        time.sleep(0.5)
        Process(target=test_child,args=(q,)).start()
    
    if __name__ == '__main__':
        gui_root = Tk()
        gui_txt = Text(gui_root)
        gui_txt.pack()
        q = StdoutQueue()
        gui_btn = Button(gui_root, text='Test', command=lambda:test_parent(q),)
        gui_btn.pack()
    
        # Instantiate and start the text monitor
        monitor = Thread(target=text_catcher,args=(gui_txt,q))
        monitor.daemon = True
        monitor.start()
    
        gui_root.mainloop()
    

    【讨论】:

    • 刚刚注意到“test_child”输出可以显示在“text_widget”中,即使该函数中没有“sys.stdout = q”。
    • @AlexZheHu 不错。我的印象是标准输出设置不会被转移到子进程,但这似乎不是真的。您可以使用sys.stdout=sys.__stdout__ 将孩子显式重定向回终端。使用装饰器可能会更清洁。
    • 我刚刚运行了您的示例,但收到以下错误:TypeError: __init__() missing 1 required keyword-only argument: 'ctx' 这是什么意思?
    • 这是一个很好的答案。但是,请小心使用Queue。比较docs.python.org/3.8/library/…docs.python.org/3.8/library/…加入使用队列的进程 部分),这使得正确拆除子进程变得更加复杂。
    【解决方案2】:

    @ebarr 给出的解决方案是正确的。但它不适用于 Python V5 或更高版本。当您尝试对multiprocessing.queues.Queue 类进行子类化时,您将收到以下错误:

    C:\Users\..\myFolder > python myTest.py
    
        Traceback (most recent call last):
            File "myTest.py", line 49, in <module>
              q = StdoutQueue()
            File "myTest.py", line 22, in __init__
              super(StdoutQueue,self).__init__(*args,**kwargs)
        TypeError: __init__() missing 1 required keyword-only argument: 'ctx'
    

    您需要为您的子类队列显式提供“多处理上下文”。

    这是更新后的代码:

    import sys
    import time
    import multiprocessing as mp
    import multiprocessing.queues as mpq
    
    from threading import Thread
    from tkinter import *
    
    '''-------------------------------------------------------------------'''
    '''                SUBCLASSING THE MULTIPROCESSING QUEUE              '''
    '''                                                                   '''
    '''         ..and make it behave as a general stdout io               '''
    '''-------------------------------------------------------------------'''
    # The StdoutQueue is a Queue that behaves like stdout.
    # We will subclass the Queue class from the multiprocessing package
    # and give it the typical stdout functions.
    #
    # (1) First issue
    # Subclassing multiprocessing.Queue or multiprocessing.SimpleQueue
    # will not work, because these classes are not genuine
    # python classes.
    # Therefore, you need to subclass multiprocessing.queues.Queue or
    # multiprocessing.queues.SimpleQueue . This issue is known, and is not
    # the reason for asking this question. But I mention it here, for
    # completeness.
    #
    # (2) Second issue
    # There is another problem that arises only in Python V5 (and beyond).
    # When subclassing multiprocessing.queues.Queue, you have to provide
    # a 'multiprocessing context'. Not doing that, leads to an obscure error
    # message, which is in fact the main topic of this question. Darth Kotik
    # solved it.
    # His solution is visible in this code:
    class StdoutQueue(mpq.Queue):
    
        def __init__(self,*args,**kwargs):
            ctx = mp.get_context()
            super(StdoutQueue, self).__init__(*args, **kwargs, ctx=ctx)
    
        def write(self,msg):
            self.put(msg)
    
        def flush(self):
            sys.__stdout__.flush()
    
    
    '''-------------------------------------------------------------------'''
    '''                           TEST SETUP                              '''
    '''-------------------------------------------------------------------'''
    
    # This function takes the text widget and a queue as inputs.
    # It functions by waiting on new data entering the queue, when it
    # finds new data it will insert it into the text widget.
    def text_catcher(text_widget,queue):
        while True:
            text_widget.insert(END, queue.get())
    
    
    def test_child(q):
        # This line only redirects stdout inside the current process
        sys.stdout = q
        # or sys.stdout = sys.__stdout__ if you want to print the child to the terminal
        print('child running')
    
    def test_parent(q):
        # Again this only redirects inside the current (main) process
        # commenting this like out will cause only the child to write to the widget
        sys.stdout = q
        print('parent running')
        time.sleep(0.5)
        mp.Process(target=test_child,args=(q,)).start()
    
    if __name__ == '__main__':
        gui_root = Tk()
        gui_txt = Text(gui_root)
        gui_txt.pack()
        q = StdoutQueue()
        gui_btn = Button(gui_root, text='Test', command=lambda:test_parent(q),)
        gui_btn.pack()
    
        # Instantiate and start the text monitor
        monitor = Thread(target=text_catcher,args=(gui_txt,q))
        monitor.daemon = True
        monitor.start()
    
        gui_root.mainloop()
    

    更多详情请参考本主题:Cannot subclass multiprocessing Queue in Python 3.5

    【讨论】:

      猜你喜欢
      • 2020-12-13
      • 1970-01-01
      • 1970-01-01
      • 2022-12-14
      • 1970-01-01
      • 1970-01-01
      • 2013-06-02
      • 2013-07-16
      • 1970-01-01
      相关资源
      最近更新 更多