线程IPython笔记本的每单元输出

时间:2013-01-18 07:05:16

标签: python web-services web-applications ipython ipython-notebook

我不想提出这个问题,因为对于什么是相当神奇的工具来说,这似乎是一个完全不合理的功能要求。但是,如果任何读者碰巧熟悉该架构,我有兴趣知道潜在的扩展是否可行。

我最近写了一个带有一些简单线程代码的笔记本,只是为了看看我运行时会发生什么。笔记本代码(tl; dr它启动了许多在睡眠循环中打印的并行线程)可在https://gist.github.com/4562840获得。

通过在代码运行时按几次SHIFT-RETURN,您可以观察到内核的任何输出都出现在当前单元格的输出区域中,而不是运行代码的单元格的输出区域。

我想知道如果线程对于单元格是活动的,是否有可能显示“刷新”按钮,允许输出区域异步更新。理想情况下,如果在所有线程结束后(最终更新后)单击它,刷新按钮将消失。

但是,这将取决于能够识别和拦截每个线程的打印输出并将其指向特定单元输出的缓冲区。所以,有两个问题。

  1. 我是否相信Python 2的print语句的硬连接意味着 使用标准解释器无法实现此增强功能吗?

  2. Python 3的前景是否更好,因为它可以潜行另一个 进入IPython内核中的print()堆栈?特别是那些没有按照Python链接到达这里的人,

  3. [没人希望西班牙宗教裁判所] 更一般地说,你能指出(与语言无关)的例子 多个流被传递到页面?有没有既定的最佳实践 用于构造和修改DOM来处理这个问题吗?

1 个答案:

答案 0 :(得分:13)

更新:

  

我是否相信Python 2的打印声明的硬连接意味着无法使用标准解释器实现此增强功能?

不,打印声明的重要部分根本没有硬连线。 print只是写入sys.stdout,它可以是writeflush方法的任何对象。 IPython已经完全替换了这个对象,以便首先将stdout输入到笔记本中(见下文)。

  

Python 3的前景是否更好,因为它可以将另一层潜入IPython内核中的print()堆栈?特别是那些没有遵循Python链接的人到这里,

Nope - 覆盖sys.stdout就是你所需要的,而不是自己打印(见上文,下面和其他地方)。 这里的Python 3没有任何优势。

  

[没人希望西班牙宗教裁判所]更一般地说,你能指出(语言无关的)多个流被传递到页面的例子吗?

当然 - IPython笔记本本身。它使用消息ID和元数据来确定stdout消息的来源, 反过来,这些消息应该结束。 下面,在我对一个显然没有人问的问题的原始答案中,我展示了一个同时绘制来自多个单元格的输出的示例,这些单元格的线程同时运行。

为了获得您想要的刷新行为,您可能需要做两件事:

  1. 将sys.stdout替换为您自己的对象,该对象使用IPython显示协议发送带有您自己的线程标识元数据的消息(例如threading.current_thread().ident)。这应该在上下文管理器中完成(如下所示),因此它只会影响您实际需要的打印语句。
  2. 编写一个IPython js插件,用于处理新格式的stdout消息,这样就不会立即绘制它们,而是存储在数组中,等待绘制。
  3. 原始答案(错误,但相关问题):

    它依赖于一些恶作剧和私有API,但这对于当前的IPython来说是完全可能的(它可能不会永远存在)。

    以下是笔记本示例:http://nbviewer.ipython.org/4563193

    为了做到这一点,你需要了解IPython如何首先将stdout添加到笔记本中。 这是通过将sys.stdout替换为OutStream对象来完成的。 这会缓冲数据,然后在调用sys.stdout.flush时通过zeromq发送数据, 它最终会在浏览器中结束。

    现在,如何将输出发送到特定单元格。

    IPython message protocol 使用父母'标头,以标识哪个请求产生了哪个回复。 每次你要求IPython运行一些代码时,它会设置各种对象的父头(包括sys.stdout), 这样他们的副作用消息就会与导致它们的消息相关联。 当您在线程中运行代码时,这意味着当前的parent_header只是最新的execute_request, 而不是原始的那个开始任何给定的线程。

    考虑到这一点,这里有一个上下文管理器,它暂时将stdout的父标题设置为特定值:

    import sys
    from contextlib import contextmanager
    
    
    stdout_lock = threading.Lock()
    
    @contextmanager
    def set_stdout_parent(parent):
        """a context manager for setting a particular parent for sys.stdout
    
        the parent determines the destination cell of output
        """
        save_parent = sys.stdout.parent_header
    
        # we need a lock, so that other threads don't snatch control
        # while we have set a temporary parent
        with stdout_lock:
            sys.stdout.parent_header = parent
            try:
                yield
            finally:
                # the flush is important, because that's when the parent_header actually has its effect
                sys.stdout.flush()
                sys.stdout.parent_header = save_parent
    

    这是一个在线程启动时记录父级的线程, 并在每次打印声明时应用该父母, 所以它的行为就像它仍然在原始单元格中一样:

    import threading
    
    class counterThread(threading.Thread):
        def run(self):
            # record the parent when the thread starts
            thread_parent = sys.stdout.parent_header
            for i in range(3):
                time.sleep(2)
                # then ensure that the parent is the same as when the thread started
                # every time we print
                with set_stdout_parent(thread_parent):
                    print i
    

    最后,笔记本将它们捆绑在一起, 时间戳显示实际并发打印到多个单元格:

    http://nbviewer.ipython.org/4563193/