如何用编程方式向IPython或Jupyter笔记本添加单元格?

3

这个问题之前已经有人问过,不过 接受的答案表明提问者想要做的与我想要做的不同。

我正在尝试创建一个IPython小部件(widget),它可以创建和运行新单元(cell)。其目的是为小部件和IPython单元(cell)之间提供相似性。

例如:我想要一个SelectorWidget,它可以用'cell magic'填充并运行下一个单元(cell)。

但是我面临的问题是(如果我理解错了,请纠正),IPython没有保留单元(cell)的内部数据结构。

在我看来,修改新单元(cell)的唯一方法是使用:

In [1]: get_ipython().set_next_input('1')

In [2]: 1

我只是希望能够自动运行那一行代码。但我认为这在set_next_input中不可能实现,因为它只是在readline的基础上进行了一些修改。


我可以使用以下方法来实现我的目标:

 display(Javacript('''
 
 var code = IPython.notebook.insert_cell_at_bottom('code'))
 code.execute()

 '''))

但是以这种方式驱动输入感觉非常不对,因为它依赖于Web浏览器来执行一些非常简单的操作。而且你还要依赖一个外部事件循环。

在命令行上进行相当的操作相当于启动一个具有访问IPython提示符的 stdin 的后台线程/进程/协程,并接受前台线程发来的任务将写入到前台线程自己的 stdin 中去。虽然可能做到,但与Jupyter方法具有相同问题,因为它特定于IPython的输入机制。

2个回答

4

看起来我无法找到没有 JavaScript 的完全同步的方法。

我找到了 run_cellrun_cell_magic 的用法,可以避免 JavaScript 示例,但不能生成带有输出的单元格。

我能找到的最接近的解决方法是:

from IPython import get_ipython

def make_cell_run_code(code):
    ipython = get_ipython()
    ipython.set_next_input(code)
    ipython.run_cell(code)

code = "print('test')"
make_cell_run_code(code)

这种方法避免了使用JavaScript,但仍然会导致单元格不同步。

通过JavaScript,我们可以使用负索引来确保执行相同的单元格。

from IPython.display import Javascript, display

def create_and_excecute_code_cell(code=''):
    display(Javascript("""
        var code = IPython.notebook.insert_cell_at_bottom('code');
        code.set_text("{0}");
        Jupyter.notebook.execute_cells([-1]);
    """.format(code)))

create_and_excecute_code_cell("%time x=0")

以下是我从这里抄来的内容。您的 JavaScript 片段对我没有起作用。

其他方法是更深入地研究IPython.core.interactiveshell.InteractiveShell


JavaScript解决方案的问题在于它取决于您使用的内核前端。您的示例将在JupyterNotebook上运行,但在JupyterLab上不起作用;它不具备可移植性。 - Rol
我正在查看InteractiveShell,它具有set_next_input函数,其行为取决于不同的shell。在Jupyter中,它会向内核发送一个ZeroMQ消息;但在控制台上,我认为它只是readline的特殊情况。 - Rol
我包含了JavaScript,以防你打算使用上面的片段。我同意,这不是一个解决方案。你需要单元格内容同步而不仅仅是在上面运行ipython.run_cell吗? - chsws
嗯,我正在尝试将Jupyter用作GUI框架。我有一个UI元素,用户可以调用它,然后可以单击创建其他UI元素的项目。但是我需要这些新的UI元素在单独的输出中。一个实际的例子:我有一个时间序列数据流列表在SelectWidget中;双击它们将把数据流添加到全局命名空间并在新单元格中执行pandas.display() - Rol

0

一种可能的但不可移植的解决方案是对处理 set_next_input 内核消息的 JavaScript 进行猴子补丁。

(function (){
    IPython.CodeCell.prototype._handle_set_next_input = function (payload) {
        var data = {
            cell: this,
            text: payload.text,
            replace: payload.replace,
            clear_output: payload.clear_output,
        execute: payload.execute
        };
        this.events.trigger('set_next_input.Notebook', data);
    };

    var that = IPython.notebook;
    // Out with the old, in with the new
    that.events.unbind("set_next_input")
    that.events.on('set_next_input.Notebook', function (event, data) {
            if (data.replace) {
                data.cell.set_text(data.text);
                if (data.clear_output !== false) {
                  // default (undefined) is true to preserve prior behavior
                  data.cell.clear_output();
                }
            } else {
                var index = that.find_cell_index(data.cell);
                var new_cell = that.insert_cell_below('code',index);
                new_cell.set_text(data.text);
            }
        
        if (data.execute && data.execute === true) {
        new_cell.execute();
        } else {
        that.dirty = true;
        }
    });
})()

然后可以在笔记本中这样调用:

with open('run_next_cell_patch.js') as f:
    # Monkey patch Jupyter with
    # set_next_input(run=True) functionality
    display(Javascript(f.read()))


from IPython import get_ipython
ip = get_ipython()
ip.payload_manager.write_payload(
        dict(
            source='set_next_input',
            text='print(1)',
            replace=False,
            execute=True
        )

)

这将在下方创建一个新的单元格,其中包含 print(1) 代码并执行其输出。这比每次插入 JavaScript 来控制输入要好一些。
问题是这只适用于 Jupyter 笔记本。仍在寻找适用于 Jupyter Lab 的解决方案。不过好在,在普通终端中解决这个问题可能相对简单。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接