跳转到内容

Voilà — 把 Jupyter Notebook 变成只显示输出的网页

待复核

Voilà 是 Jupyter 生态里的开源发布工具(QuantStack 等贡献者推动,BSD-3-Clause),2019 年 6 月公开,仓库在 github.com/voila-dashboards/voila。日常类比:像把一份 Jupyter Notebook 复印后裁掉所有代码块,只留图表、表格和滑块,再给这张纸发一个网址——不会写代码的同事点开就能用。

最小体验:

Terminal window
pip install voila
voila my-notebook.ipynb
# 浏览器打开 http://localhost:8866

页面上看不到代码 cell,只有输出和控件。访客拖滑块时,后台仍有一个活着的 Python 进程在算——不是纯静态 HTML。交付链路:写 notebook → voila → 同事拿 URL。没碰 Flask、没写前端、没改 notebook

不理解 Voilà,下面这些事都没法解释:

  • 为什么 Jupyter 教学站能把”交互式教材”直接当网页发——不必二次开发
  • 为什么数据科学家做内部看板会先想到它而不是 Flask + Plotly——探索代码原封不动就能交付
  • 为什么 streamlit 火了之后它仍有位置——.ipynb 用户群不愿把代码搬到 .py 重写
  • 为什么后来有 voici(浏览器版,跑在 Pyodide 上)——把”零后端”再推到极致

设计哲学是做最薄的发布层:复用 Jupyter 已有能力,只补”隐藏代码、暴露页面”。记三件事:

  1. 一人一厨房(kernel-per-session):每个访客来访,后台单独开一个 Python 进程(kernel)给他跑这份 notebook。类比:每人进店就开一间独立厨房,互不串味。

  2. 先做菜再藏菜谱(预执行 + 隐藏源码):请求到来后,用 nbconvert 把所有 cell 跑一遍,再把代码(source)剔掉,只留 outputs。默认 --strip_sources=True;交互仍靠活 kernel,不是把结果烤成死页面。

  3. 滑块走对讲机(ipywidgets Comm):访客动控件,浏览器经 WebSocket / Comm 把新值推到 kernel,回调跑完再推回前端——和 JupyterLab 里拖滑块是同一条路。外观用 --template lab|material|vuetify 切换。

import pandas as pd, ipywidgets as W
from IPython.display import display
df = pd.read_csv("sales.csv")
region = W.Dropdown(options=df.region.unique(), description="地区")
out = W.Output()
def refresh(_=None):
out.clear_output()
with out:
display(df[df.region == region.value].head(20))
region.observe(refresh, "value")
refresh()
display(region, out)

逐部分解释

  1. Dropdown 列出地区;Output 是表格要刷新的”画框”
  2. refresh 按当前地区过滤并 display 前 20 行
  3. observe 把”下拉一变 → 重画”接上;voila sales.ipynb 后同事只看到控件+表,看不到代码

案例 2:轻量推理 demo(不依赖大模型下载)

Section titled “案例 2:轻量推理 demo(不依赖大模型下载)”
import ipywidgets as W
from IPython.display import display
text = W.Textarea(placeholder="输入一句话")
btn = W.Button(description="判断情感")
out = W.Output()
def run(_):
out.clear_output()
score = sum(1 for w in ("", "", "喜欢") if w in text.value)
with out:
print("偏正面" if score else "偏中性/负面")
btn.on_click(run)
display(text, btn, out)

逐部分解释

  1. 文本框 + 按钮收集输入;真实项目可换成本地小模型,教学先用规则避免首次下载卡死
  2. on_click 在 kernel 里跑 run,结果写进 out
  3. voila demo.ipynb --port 7860 --no-browser,把 URL 发给非工程同事即可
Terminal window
pip install voici
voici build my-notebook.ipynb --output dist/

dist/ 是 HTML + WASM Python(Pyodide),可丢 GitHub Pages。完全没有后端;代价是包生态比 CPython 小——pandas/numpy 通常能跑,部分 C 扩展不行。

  1. 一人一 kernel = 内存吃紧:100 人在线 ≈ 100 个 Python 进程;生产常前置 JupyterHub,并开闲置回收。
  2. 首屏预执行慢:整本 notebook 先跑完才出页;数据读取可 @lru_cache,或 voila --pre_heat_kernel=True
  3. ipywidgets 版本错配:notebook 写 8.x、环境装 7.x 会报 model_id 找不到——三件套锁同一代。
  4. 无内置认证:公网必须 nginx Basic Auth / JupyterHub / IP 白名单,否则等于开放后端进程。

适用

  • 教学 / 科研 / 量化——已有 notebook 文化,零成本上线
  • 单团队内部小看板——访问量低(大约几十人同时在线可接受)
  • 交互式教材 + voici 做成纯静态站
  • HuggingFace Spaces 轻量 demo(平台代管 kernel)

不适用

  • 高并发 C 端——kernel-per-session 扛不住万级 QPS
  • 多用户协作 / 复杂权限——要套 JupyterHub
  • 复杂前端交互——自定义 ipywidgets 工作量反超 react
  • 重逻辑应用——选 streamlit / gradio / dash 更顺
  • 2015–2018:ipywidgets 让 notebook 里的滑块可交互,但分享仍要”打开我的 .ipynb
  • 2019-06:QuantStack 等在 Jupyter 博客发 And voilà!,把”只显示输出的网页”做成独立工具
  • 之后:模板生态(lab / material / vuetify)与 JupyterHub 部署路径成熟;voici 再把同一思路推到 Pyodide 静态站
  1. 最薄发布层往往比再造框架更长寿:只补”notebook → URL”,反而站稳 Jupyter 用户群
  2. 执行模型决定成本:选 .ipynb 就继承”每访客一进程”——早期耦合贯穿一生
  3. 复用既有协议便宜:Comm / nbconvert / kernel 直接搬上生产页
  4. 零后端是更激进形态:voici 证明发布层可薄到无服务器,代价是包生态裁剪
  • streamlit —— 同样”Python → Web”,但选 .py + 重跑模型
  • gradio —— ML demo 友好型,函数直接绑控件
  • panel —— 同从 Jupyter 出发,但带应用框架结构
  • dash —— Plotly 系,回调式 API
  • jupyter-notebook —— 输入格式与执行内核来源
  • react —— 前端 widget 实现栈