无法在Python 3.5中

时间:2016-09-14 17:39:24

标签: python python-3.x multiprocessing python-multiprocessing

我最终的目标是将stdout从几个子进程重定向到某些队列,并将其打印到某处(可能在一个小GUI中)。

第一步是将Queue子类化为一个与stdout非常相似的对象。但那是我被卡住的地方。在Python v3.5中,似乎不可能对多处理Queue进行子类化。

# This is a Queue that behaves like stdout
# Unfortunately, doesn't work in Python 3.5   :-(
class StdoutQueue(Queue):
    def __init__(self,*args,**kwargs):
        Queue.__init__(self,*args,**kwargs, ctx='')

    def write(self,msg):
        self.put(msg)

    def flush(self):
        sys.__stdout__.flush()

我在以下帖子中找到了这个片段(当时可能还没有Python 3.5):Python multiprocessing redirect stdout of a child process to a Tkinter Text

在Python v3.5中,当您对多处理Queue类进行子类化时,您会发现奇怪的错误消息。我发现了两个描述该问题的错误报告:

https://bugs.python.org/issue21367

https://bugs.python.org/issue19895

我有两个问题:

  1. 假设我想坚持使用Python v3.5 - 转到以前的版本并不是一个真正的选择。我可以使用哪种解决方法来以某种方式对多处理队列进行子类化?
  2. 如果我升级到Python v3.6,问题仍然存在吗?
  3. 编辑:

    当您尝试子类化此处的Queue类时,存在一个已知问题:

    from multiprocessing import Queue    # <- known issue: you cannot subclass
                                         #    this Queue class, because it is
                                         #    not a genuine python class.
    

    但以下情况应该有效:

    from multiprocessing.queues import Queue   # <- from this Queue class, you
                                               #    should be able to make a
                                               #    subclass. But Python 3.5
                                               #    refuses :-(
    

    可悲的是,即使这样也不适用于Python v3.5。您收到以下错误:

        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'
    

    编辑:

    感谢Darth Kotik解决问题!这是完整的代码,用他的解决方案更新。现在它有效。

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

1 个答案:

答案 0 :(得分:4)

>>> import multiprocessing
>>> type(multiprocessing.Queue)
<class 'method'>
AttributeError: module 'multiprocessing' has no attribute 'queues'
>>> import multiprocessing.queues
>>> type(multiprocessing.queues.Queue)
<class 'type'>

因此,您可以看到multiprocessing.Queue只是multiprocessing.queues.Queue类的构造函数方法。如果你想做一个儿童班,只需做class MyQueue(multiprocessing.queues.Queue)

您可以看到此方法的来源here

修改: 好的。我现在遇到了你的问题。正如您在上面的链接中看到的那样,multiprocessing.Queuectx参数传递给Queue。所以我设法通过__init__方法自己完成它。我没有完全理解BaseContext对象应该获得_name属性的位置,所以我手动传递了它。

def __init__(self,*args,**kwargs):
    from multiprocessing.context import BaseContext
    ctx = BaseContext()
    ctx._name = "Name"
    super(StdoutQueue,self).__init__(*args,**kwargs, ctx=ctx)

EDIT2 :结果文档中包含有关上下文here的一些信息。因此,不像我那样手动创建它,你可以做到

import multiprocessing
ctx = multiprocessing.get_context()

它会创建适当的上下文_name set(在你的特定情况下为'fork'),你可以将它传递给你的队列。