Вывод на ячейку для многопоточных ноутбуков IPython

Я не хочу поднимать это как проблему, потому что это кажется совершенно необоснованным запросом функции для довольно удивительного инструмента. Но если кто-то из читателей знаком с архитектурой, мне было бы интересно узнать, возможно ли потенциальное расширение.

Недавно я написал записную книжку с простым многопоточным кодом, просто чтобы посмотреть, что произойдет, когда я ее запущу. Код записной книжки (tl;dr запускает ряд параллельных потоков, которые печатаются в спящем цикле) доступен по адресу https://gist.github.com/4562840.

Нажав SHIFT-RETURN несколько раз во время выполнения кода, вы увидите, что любые выходные данные ядра появляются в области вывода текущей ячейки, а не той ячейки, в которой выполнялся код.

Мне было интересно, возможно ли, если потоки были активны для ячейки, отображать кнопку «обновить», позволяющую асинхронно обновлять область вывода. В идеале кнопка обновления исчезла бы, если бы она была нажата после завершения всех потоков (после окончательного обновления).

Однако это будет зависеть от возможности идентифицировать и перехватывать вывод на печать для каждого потока и направлять его в буфер для вывода конкретной ячейки. Итак, два вопроса.

  1. Прав ли я, полагая, что жесткое подключение оператора печати Python 2 означает, что это улучшение не может быть реализовано с помощью стандартного интерпретатора?

  2. Будут ли перспективы для Python 3 лучше, учитывая, что можно внедрить еще один слой в стек print() внутри ядра IPython? Особенно для тех, кто не перешел по ссылке Python, чтобы попасть сюда,

  3. [никто не ожидает испанской инквизиции] В более общем смысле, можете ли вы указать на (не зависящие от языка) примеры доставки нескольких потоков на страницу? Существуют ли какие-либо установленные передовые методы создания и изменения DOM для решения этой проблемы?


person holdenweb    schedule 18.01.2013    source источник
comment
Я бы попробовал возиться с sys.displayhook(), чтобы посмотреть, сможете ли вы куда-нибудь добраться.   -  person ncoghlan    schedule 18.01.2013
comment
В качестве альтернативы можно заменить sys.stdout настраиваемым объектом, который играет в игры (печать по-прежнему проходит через sys.stdout в версии 2.x, так что вы можете изменить его поведение глобально).   -  person ncoghlan    schedule 18.01.2013


Ответы (1)


ОБНОВИТЬ:

Прав ли я, полагая, что жесткое подключение оператора печати Python 2 означает, что это улучшение не может быть реализовано с помощью стандартного интерпретатора?

Нет, важные части оператора печати вообще не запрограммированы. print просто пишет в sys.stdout, который может быть любым объектом с методами write и flush. IPython уже полностью заменяет этот объект, чтобы в первую очередь получить стандартный вывод на блокнот (см. ниже).

Будут ли перспективы для Python 3 лучше, учитывая, что можно внедрить еще один слой в стек print() внутри ядра IPython? Особенно для тех, кто не перешел по ссылке Python, чтобы попасть сюда,

Нет, вам нужно переопределить sys.stdout, а не саму печать (см. выше, ниже и в других местах). Преимуществ у Python 3 здесь нет.

[никто не ожидает испанской инквизиции] В более общем смысле, можете ли вы указать на (не зависящие от языка) примеры доставки нескольких потоков на страницу?

Конечно, сам ноутбук IPython. Он использует идентификаторы сообщений и метаданные для определения источника сообщений stdout и, в свою очередь, того, где эти сообщения должны заканчиваться. Ниже, в моем первоначальном ответе на вопрос, который, по-видимому, никто не задавал, я показываю пример одновременного рисования вывода, поступающего из нескольких ячеек, чьи потоки выполняются одновременно.

Чтобы получить желаемое поведение обновления, вам, вероятно, потребуется сделать две вещи:

  1. замените sys.stdout своим собственным объектом, который использует протокол отображения IPython для отправки сообщений с вашими собственными метаданными, идентифицирующими поток (например, threading.current_thread().ident). Это должно быть сделано в диспетчере контекста (как показано ниже), поэтому оно влияет только на операторы печати, которые вы действительно хотите.
  2. напишите плагин IPython js для обработки вашего нового формата сообщений stdout, чтобы они не рисовались сразу, а сохранялись в массивах, ожидая рисования.

Оригинальный ответ (неправильный, но связанный вопрос):

Он основан на некоторых махинациях и частных API, но это вполне возможно с текущим IPython (возможно, это не навсегда).

Вот пример блокнота: http://nbviewer.ipython.org/4563193.

Чтобы сделать это, вам нужно понять, как IPython получает стандартный вывод на блокнот. Это делается путем замены sys.stdout объектом OutStream. Это буферизует данные, а затем отправляет их через zeromq при вызове sys.stdout.flush, и в конечном итоге они оказываются в браузере.

Теперь, как отправить вывод в конкретную ячейку.

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

А вот поток, который записывает родителя при запуске потока и применяет этот родитель каждый раз, когда выполняет оператор печати, поэтому он ведет себя так, как если бы он все еще находился в исходной ячейке:

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/

person minrk    schedule 18.01.2013
comment
упс, я слишком погорячился. Я думаю, что ответил совершенно на другой вопрос, но это то, что пришло мне в голову, когда я увидел «нитки + печать в блокноте». Я вернусь с некоторыми изменениями завтра. В тетради ответ на можно ли? почти всегда да, но.... - person minrk; 18.01.2013
comment
Спасибо, это был отличный ответ, и он рассказал мне все, что мне нужно было знать. Я надеюсь стать более знакомым с архитектурой. Спасибо за исчерпывающий и содержательный ответ. - person holdenweb; 20.01.2013
comment
minrk, я попытался запустить ваш ноутбук на своем ноутбуке (Ubuntu 13.04, Python 2.7.4), но это не сработало, как ожидалось. Когда я запускаю все ячейки, сначала ничего не происходит. Затем, когда я добавляю одну новую ячейку ниже и запускаю ее с простой печатью внутри, ее вывод содержит все распечатки всех потоков. Не могли бы вы помочь мне? - person Rizar; 30.01.2014
comment
@Rizar Я знаю, что это старо, но у тебя когда-нибудь это работало? Я использую Jupyter==4.1.0/Python==2.7.10, и, похоже, он не работает. Я получаю те же результаты, что и вы. - person tmthyjames; 12.11.2016
comment
@minrk Вы говорите, что это может быть невозможно в более поздних версиях IPython. Возможно ли это в Jupyter 4.1? В этой версии вроде не работает. Если нет, есть ли что-нибудь подобное, что могло бы отразить это поведение? - person tmthyjames; 16.11.2016
comment
@minrk Меня тоже интересует обновление, но оно тоже не работает - person Bananach; 04.04.2019
comment
Только что попробовал связанный блокнот на Python 3.7.6 и Jupyter Notebook 6.0.3 - кажется, он работает нормально, когда оператор print в определении CounterThread обновляется до вызова print(). - person holdenweb; 03.02.2021