无法让Jupyter笔记本访问javascript变量



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

stackoverflow上也有类似的问题,例如:IPython Notebook Javascript:从Javascript变量中检索内容。根据我目前的发现,这个问题没有解决办法。我觉得这非常令人失望,因为创建可以从Jupyter笔记本电脑直接访问的Web服务,而不必走node.js路线,这将是非常酷的。

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

下面的代码将Javascript插入到页面中:"sendRequest"功能只有5秒的延迟,但它旨在通过window.postMessage.与另一个网页进行通信

futureResponse是一个异步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延迟(5s(后调用responseHandler(Javascript(,它调用kernel.execute,后者调用responseHandler(python(,后者设置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)

如果在上一个单元格之后立即计算下面的单元格(如在Run all场景中(,则futureResponse仍然未解析。但如果在5秒钟后进行评估,futureResponse的值为"RESPONSE"。

futureResponse

我对此进行了进一步的研究,并找到了解决方案,但这确实是一个可能会与Jupyter笔记本的未来版本决裂的黑客攻击。我希望有人能有一个更经得起未来考验的答案。

这个想法是为了避免代码示例通过使用Jupyter消息传递系统的stdin通道创建的第二十二条陷阱(https://jupyter-client.readthedocs.io/en/stable/messaging.html)。

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

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"并继续。

最新更新