运行 Jupyter Notebook 中的 Tornado 服务器
Running a Tornado Server within a Jupyter Notebook
采用标准的 Tornado 演示并将 IOLoop 推入后台线程允许在单个脚本中查询服务器。这在 Tornado 服务器是交互式对象时很有用(参见 Dask 或类似内容)。
import asyncio
import requests
import tornado.ioloop
import tornado.web
from concurrent.futures import ThreadPoolExecutor
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
pool = ThreadPoolExecutor(max_workers=2)
loop = tornado.ioloop.IOLoop()
app = make_app()
app.listen(8888)
fut = pool.submit(loop.start)
print(requests.get("https://localhost:8888"))
以上在标准 python 脚本中工作得很好(尽管它缺少安全关闭)。 Jupyter Notebook 是这些交互式 Tornado 服务器环境的最佳环境。然而,当涉及到 Jupyter 时,这个想法就被打破了,因为已经有一个活跃的 运行 循环:
>>> import asyncio
>>> asyncio.get_event_loop()
<_UnixSelectorEventLoop running=True closed=False debug=False>
当 运行 在 Jupyter notebook 中执行上述脚本时,服务器和请求客户端都试图在同一个线程中打开连接,代码挂起。构建一个新的 Asyncio 循环 and/or Tornado IOLoop 似乎没有帮助,我怀疑我在 Jupyter 本身中遗漏了一些东西。
问题:是否可以在 Jupyter notebook 的后台有一个实时 Tornado 服务器 运行,以便标准 python requests
或类似的服务器可以从主线程?我宁愿尽可能避免在呈现给用户的代码中使用 Asyncio,因为它对新手用户来说相对复杂。
您可以使用 %%script --bg
魔术命令在后台创建龙卷风服务器 运行。选项 --bg
告诉 jupyter 运行 后台当前单元格的代码。
只需在一个单元格中创建一个龙卷风服务器以及魔术命令和运行那个单元格。
示例:
%%script python --bg
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
loop = tornado.ioloop.IOLoop.current()
app = make_app()
app.listen(8000) # 8888 was being used by jupyter in my case
loop.start()
然后您可以在单独的单元格中使用 requests
连接到服务器:
import requests
print(requests.get("http://localhost:8000"))
# prints <Response [200]>
这里要注意的一件事是,如果您 stop/interrupt 任何单元格上的内核,后台脚本也会停止。因此,您必须再次 运行 此单元格才能启动服务器。
第 1 部分:获取嵌套龙卷风
要找到您需要的信息,您必须遵循以下 crumbtrails,首先查看 IPython 7 的发行说明中描述的内容
特别是它将向您指出有关 documentation, and to this discussion 中 async 和 await 部分的更多信息,
建议使用 nest_asyncio.
症结如下:
- A) 要么你把 python 骗到 运行 两个嵌套的事件循环中。 (nest_asyncio 做什么)
- B) 你在已经存在的事件循环上安排协程。 (我不确定如何用龙卷风做到这一点)
我很确定你知道所有这些,但我相信其他 reader 会很感激。
不幸的是,没有办法让它对用户完全透明——好吧,除非你像在 jupyterhub 上那样控制部署,并且可以将这些行添加到自动加载的 IPython 启动脚本中。不过我觉得下面的就够简单了。
import nest_asyncio
nest_asyncio.apply()
# rest of your tornado setup and start code.
第 2 部分:Gotcha 同步代码块 eventloop。
上一节只关心能够运行 tornado 应用程序。但请注意,任何同步代码都会阻塞事件循环;因此,当 运行ning print(requests.get("http://localhost:8000"))
服务器似乎无法工作,因为您正在阻止事件循环,只有当等待事件循环重新启动的代码完成执行时,它才会重新启动...(理解这一点是留给 reader) 的练习。您需要从另一个内核 发出 print(requests.get("http://localhost:8000"))
,或者使用 aiohttp。
以下是如何以与请求类似的方式使用 aiohttp。
import aiohttp
session = aiohttp.ClientSession()
await session.get('http://localhost:8889')
在这种情况下,由于 aiohttp 是非阻塞的,因此一切似乎都能正常工作。您可以在这里看到一些额外的 IPython 魔法,我们在当前事件循环中自动检测异步代码并 运行 它。
一个很酷的练习可以是 运行 request.get
在另一个内核的循环中, 运行 sleep(5)
在 tornado 所在的内核 运行ning,看到我们停止处理请求...
第 3 部分:免责声明和其他路线:
这相当棘手,我建议不要在生产中使用,并警告您的用户这不是推荐的做事方式。
这并不能完全解决你的问题,你需要 运行 不在主线程中的事情,我不确定是否可行。
您也可以尝试与其他循环 运行ners 一起玩,比如 trio and curio; they might allow you to do stuff you can't with asyncio by default like nesting, but here be dragoons. I highly recommend trio and the multiple blog posts around its creation,特别是如果您正在教授异步。
祝您愉快,希望对您有所帮助,请报告错误以及有效的方法。
根据我的 recent PR to streamz,这里有一些与您的想法相似的东西:
class InNotebookServer(object):
def __init__(self, port):
self.port = port
self.loop = get_ioloop()
self.start()
def _start_server(self):
from tornado.web import Application, RequestHandler
from tornado.httpserver import HTTPServer
from tornado import gen
class Handler(RequestHandler):
source = self
@gen.coroutine
def get(self):
self.write('Hello World')
application = Application([
('/', Handler),
])
self.server = HTTPServer(application)
self.server.listen(self.port)
def start(self):
"""Start HTTP server and listen"""
self.loop.add_callback(self._start_server)
_io_loops = []
def get_ioloop():
from tornado.ioloop import IOLoop
import threading
if not _io_loops:
loop = IOLoop()
thread = threading.Thread(target=loop.start)
thread.daemon = True
thread.start()
_io_loops.append(loop)
return _io_loops[0]
调用笔记本
In [2]: server = InNotebookServer(9005)
In [3]: import requests
requests.get('http://localhost:9005')
Out[3]: <Response [200]>
采用标准的 Tornado 演示并将 IOLoop 推入后台线程允许在单个脚本中查询服务器。这在 Tornado 服务器是交互式对象时很有用(参见 Dask 或类似内容)。
import asyncio
import requests
import tornado.ioloop
import tornado.web
from concurrent.futures import ThreadPoolExecutor
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
pool = ThreadPoolExecutor(max_workers=2)
loop = tornado.ioloop.IOLoop()
app = make_app()
app.listen(8888)
fut = pool.submit(loop.start)
print(requests.get("https://localhost:8888"))
以上在标准 python 脚本中工作得很好(尽管它缺少安全关闭)。 Jupyter Notebook 是这些交互式 Tornado 服务器环境的最佳环境。然而,当涉及到 Jupyter 时,这个想法就被打破了,因为已经有一个活跃的 运行 循环:
>>> import asyncio
>>> asyncio.get_event_loop()
<_UnixSelectorEventLoop running=True closed=False debug=False>
当 运行 在 Jupyter notebook 中执行上述脚本时,服务器和请求客户端都试图在同一个线程中打开连接,代码挂起。构建一个新的 Asyncio 循环 and/or Tornado IOLoop 似乎没有帮助,我怀疑我在 Jupyter 本身中遗漏了一些东西。
问题:是否可以在 Jupyter notebook 的后台有一个实时 Tornado 服务器 运行,以便标准 python requests
或类似的服务器可以从主线程?我宁愿尽可能避免在呈现给用户的代码中使用 Asyncio,因为它对新手用户来说相对复杂。
您可以使用 %%script --bg
魔术命令在后台创建龙卷风服务器 运行。选项 --bg
告诉 jupyter 运行 后台当前单元格的代码。
只需在一个单元格中创建一个龙卷风服务器以及魔术命令和运行那个单元格。
示例:
%%script python --bg
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
loop = tornado.ioloop.IOLoop.current()
app = make_app()
app.listen(8000) # 8888 was being used by jupyter in my case
loop.start()
然后您可以在单独的单元格中使用 requests
连接到服务器:
import requests
print(requests.get("http://localhost:8000"))
# prints <Response [200]>
这里要注意的一件事是,如果您 stop/interrupt 任何单元格上的内核,后台脚本也会停止。因此,您必须再次 运行 此单元格才能启动服务器。
第 1 部分:获取嵌套龙卷风
要找到您需要的信息,您必须遵循以下 crumbtrails,首先查看 IPython 7 的发行说明中描述的内容 特别是它将向您指出有关 documentation, and to this discussion 中 async 和 await 部分的更多信息, 建议使用 nest_asyncio.
症结如下:
- A) 要么你把 python 骗到 运行 两个嵌套的事件循环中。 (nest_asyncio 做什么)
- B) 你在已经存在的事件循环上安排协程。 (我不确定如何用龙卷风做到这一点)
我很确定你知道所有这些,但我相信其他 reader 会很感激。
不幸的是,没有办法让它对用户完全透明——好吧,除非你像在 jupyterhub 上那样控制部署,并且可以将这些行添加到自动加载的 IPython 启动脚本中。不过我觉得下面的就够简单了。
import nest_asyncio
nest_asyncio.apply()
# rest of your tornado setup and start code.
第 2 部分:Gotcha 同步代码块 eventloop。
上一节只关心能够运行 tornado 应用程序。但请注意,任何同步代码都会阻塞事件循环;因此,当 运行ning print(requests.get("http://localhost:8000"))
服务器似乎无法工作,因为您正在阻止事件循环,只有当等待事件循环重新启动的代码完成执行时,它才会重新启动...(理解这一点是留给 reader) 的练习。您需要从另一个内核 发出 print(requests.get("http://localhost:8000"))
,或者使用 aiohttp。
以下是如何以与请求类似的方式使用 aiohttp。
import aiohttp
session = aiohttp.ClientSession()
await session.get('http://localhost:8889')
在这种情况下,由于 aiohttp 是非阻塞的,因此一切似乎都能正常工作。您可以在这里看到一些额外的 IPython 魔法,我们在当前事件循环中自动检测异步代码并 运行 它。
一个很酷的练习可以是 运行 request.get
在另一个内核的循环中, 运行 sleep(5)
在 tornado 所在的内核 运行ning,看到我们停止处理请求...
第 3 部分:免责声明和其他路线:
这相当棘手,我建议不要在生产中使用,并警告您的用户这不是推荐的做事方式。
这并不能完全解决你的问题,你需要 运行 不在主线程中的事情,我不确定是否可行。
您也可以尝试与其他循环 运行ners 一起玩,比如 trio and curio; they might allow you to do stuff you can't with asyncio by default like nesting, but here be dragoons. I highly recommend trio and the multiple blog posts around its creation,特别是如果您正在教授异步。
祝您愉快,希望对您有所帮助,请报告错误以及有效的方法。
根据我的 recent PR to streamz,这里有一些与您的想法相似的东西:
class InNotebookServer(object):
def __init__(self, port):
self.port = port
self.loop = get_ioloop()
self.start()
def _start_server(self):
from tornado.web import Application, RequestHandler
from tornado.httpserver import HTTPServer
from tornado import gen
class Handler(RequestHandler):
source = self
@gen.coroutine
def get(self):
self.write('Hello World')
application = Application([
('/', Handler),
])
self.server = HTTPServer(application)
self.server.listen(self.port)
def start(self):
"""Start HTTP server and listen"""
self.loop.add_callback(self._start_server)
_io_loops = []
def get_ioloop():
from tornado.ioloop import IOLoop
import threading
if not _io_loops:
loop = IOLoop()
thread = threading.Thread(target=loop.start)
thread.daemon = True
thread.start()
_io_loops.append(loop)
return _io_loops[0]
调用笔记本
In [2]: server = InNotebookServer(9005)
In [3]: import requests
requests.get('http://localhost:9005')
Out[3]: <Response [200]>