9

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

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

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

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

不过,这将取决于能否识别和截取每个线程的打印输出,并将其定向到特定单元格输出的缓冲区。所以,两个问题。

  1. 我是否正确地相信 Python 2 的 print 语句的硬连线意味着这种增强不能用标准解释器来实现?

  2. Python 3 的前景会更好吗,因为可以将另一层潜入 IPython 内核中的 print() 堆栈中?尤其是对于那些没有通过 Python 链接到达这里的人,

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

4

1 回答 1

14

更新:

我是否正确地相信 Python 2 的 print 语句的硬连线意味着这种增强不能用标准解释器来实现?

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

Python 3 的前景会更好吗,因为可以将另一层潜入 IPython 内核中的 print() 堆栈中?尤其是对于那些没有通过 Python 链接到达这里的人,

不-覆盖 sys.stdout 是您所需要的,而不是打印本身(见上文、下文和其他地方)。Python 3 在这里没有任何优势。

[没有人期待西班牙宗教裁判所] 更一般地说,您能否指出(与语言无关的)多个流被传送到一个页面的示例?

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

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

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

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

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

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

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

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

IPython消息协议 使用“父”标头来识别哪个请求产生了哪个回复。每次您要求 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

这是一个 Thread ,它在线程启动时记录父级,并在每次发出 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

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

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

于 2013-01-18T08:54:49.687 回答