【发布时间】: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