【问题标题】:Cannot get Jupyter notebook to access javascript variables无法让 Jupyter 笔记本访问 javascript 变量
【发布时间】:2019-11-15 16:44:19
【问题描述】:

我有一个做文件格式转换的网站,这个服务可以通过window.postMessage系统被其他网站使用。如果一个人也可以通过 Jupyter 笔记本做到这一点,那就太好了。然而,我遇到了一个严重的问题。我可以让 python 创建一个 Javascript 命令,将格式 A 的文件发送到网站,我可以在延迟一段时间后访问 Javascript 中的响应(格式 B 的文件),但我无法将 Javascript 的响应返回到 Python 中进一步处理。我制作了一个尽可能简单的笔记本来演示这个问题。原因似乎是 Python 首先执行所有单元格(在 Run all 场景中),然后才查看任何新的执行请求。

stackoverflow 上有类似的问题,例如这里: IPython Notebook Javascript: retrieve content from JavaScript variables。 从我目前发现的情况来看,这个问题没有解决办法。我觉得这非常令人失望,因为创建可从 Jupyter 笔记本直接访问的 Web 服务真是太酷了,而不必走 node.js 路线。

import asyncio, time
from IPython.display import display, Javascript

以下代码将 Javascript 插入页面: 'sendRequest' 函数只是延迟了 5 秒,但它的目的是通过 window.postMessage 与另一个网页进行通信。

futureResponse 是一个 asyncio Future (python)。当 sendRequest (Javascript) 完成并使用 kernel.execute (Javascript) 调用 responseHandler (python) 时,它会得到一个结果。

global futureResponse

def responseHandler(response):
  futureResponse.set_result(response)

display(Javascript("""
  window.responseHandler = (response) => {
    var kernel = IPython.notebook.kernel;
    var pyCommand = 'responseHandler(\\''+response+'\\')';
    kernel.execute(pyCommand);
    console.log('responseHandler called')
  };
  window.sendRequest = () => {
    return new Promise( (resolve,reject) => { setTimeout(()=>resolve('RESPONSE'),5000) } )
  }
"""))

下面的代码插入了调用 sendRequest 的 Javascript。这将在 setTimeout 延迟(5 秒)之后调用 responseHandler(Javascript),它调用 kernel.execute 调用 responseHandler(python),它设置 futureResponse 的结果。 但是,如果我使用“await futureResponse”,它永远不会实现。

# the value of futureResponse will be set by the responseHandler
futureResponse = asyncio.Future()

display(Javascript("""
window.sendRequest().then(
  (response) => window.responseHandler(response)
)
"""))

# This does not work (futureResponse unresolved):
time.sleep(6)

# With await, the notebook keeps waiting forever.
#await futureResponse

print(futureResponse)

如果在前一个单元格之后立即评估下面的单元格(如在全部运行场景中),则 futureResponse 仍未解决。但如果在 5 秒后评估,futureResponse 的值为“RESPONSE”。

futureResponse

【问题讨论】:

  • 如果你使用 asyncio,你不应该调用time.sleep(6),你应该调用await asyncio.sleep(6)(或者简单的await futureResponse)。您是否检查过 kernel.execute() 是否支持 asyncio,即它是否在 asyncio 线程中运行回调?如果没有,您应该使用loop.call_soon_threadsafe(futureResponse.set_result, response) 以确保在设置结果时唤醒事件循环。 (Asyncio 期货只能在运行 asyncio 事件循环的线程中使用。)
  • 感谢您的想法;我实施了它们,但无济于事。我认为问题在于代码单元的执行和这些单元生成的附加请求是通过先进先出队列完成的。如果我在上面的代码中取消注释“等待 futureResponse”,则会出现一个 catch-22:futureResponse 在所有单元格运行后得到解决,但单元格正在等待 futureResponse 解决。我查看了 Jupyter notebook 消息系统,发现还有其他消息通道(shell、iopub、control、comm、input)可以玩。我试图在下面的答案中利用这一点。

标签: javascript python jupyter-notebook python-asyncio


【解决方案1】:

我对此进行了进一步研究并找到了一个解决方案,但它确实是一个可能会在 Jupyter notebook 的未来版本中破坏的 hack。我希望有人有一个更面向未来的答案。

这个想法是为了避免代码示例使用 Jupyter 消息系统 (https://jupyter-client.readthedocs.io/en/stable/messaging.html) 的标准输入通道创建的 catch-22。

代码的工作原理如下: 当代码单元包含input('prompt-text') 命令时,将通过专用的标准输入通道将其发送到笔记本客户端,该通道独立于正常的单元执行流。因此,我“破解”了笔记本客户端的代码,以捕获内核发送的输入请求,并在不提示用户的情况下提供答案。

display(Javascript("""
  const CodeCell = window.IPython.CodeCell;

  CodeCell.prototype.native_handle_input_request = CodeCell.prototype.native_handle_input_request || CodeCell.prototype._handle_input_request
  CodeCell.prototype._handle_input_request = function(msg) {
    try {
      // only apply the hack if the command is valid JSON
      const command = JSON.parse(msg.content.prompt);
      const kernel = IPython.notebook.kernel;
      // in the future: send the command to a server and wait for a response.
      // for now: specify a 5 second delay and return 'RESPONSE'
      setTimeout(() => { kernel.send_input_reply('RESPONSE') },5000)
    } catch(err) {
      console.log('Not a command',msg,err);
      this.native_handle_input_request(msg);
    }
  }
"""))
response = input(json.dumps({"do":"something"}))
print(response)

这会阻止执行 5 秒,然后打印 'RESPONSE' 并继续。

【讨论】:

    猜你喜欢
    • 2018-08-20
    • 2013-02-08
    • 1970-01-01
    • 2021-04-14
    • 2020-11-18
    • 2018-10-07
    • 2018-07-28
    • 2018-02-26
    相关资源
    最近更新 更多