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

hol*_*web 8 python web-services web-applications ipython ipython-notebook

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

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

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

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

但是,这将取决于能够识别和拦截每个线程的打印输出并将其引导到特定单元输出的缓冲区.那么,有两个问题.

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

  2. 鉴于可以将另一层潜入IPython内核中的print()堆栈,并且特别是对于那些没有按照Python链接到达的人来说,Python 3的前景是否更好?

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

min*_*nrk 13

更新:

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

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

鉴于可以将另一层潜入IPython内核中的print()堆栈,并且特别是对于那些没有按照Python链接到达的人来说,Python 3的前景是否更好?

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

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

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

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

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

原始答案(错误,但相关问题):

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

这是一个示例笔记本:http://nbviewer.ipython.org/4563193

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

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

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

考虑到这一点,这里有一个上下文管理器,它暂时将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
Run Code Online (Sandbox Code Playgroud)

这是一个线程,它在线程启动时记录父项,并在每次创建一个print语句时应用该父项,因此它的行为就像它仍然在原始单元格中一样:

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
Run Code Online (Sandbox Code Playgroud)

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

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

  • 哎呀,我有点太兴奋了。我想我回答了一个*完全*不同的问题,但这就是我看到“笔记本中的线程 + 打印”时想到的。明天我会回来做一些修改。在笔记本中,回答“有可能吗?” 几乎总是“是的,但是……”。 (2认同)