为 Jupyter 制作内核

‘内核’ 是一个运行并自省用户代码的程序。IPython 包括一个用于 Python 代码的内核,人们已经为 其他几种语言编写了内核

在内核启动时,Jupyter 向内核传递一个连接文件。这指定了如何设置与前端的通信。

编写内核有三种选择:

  1. 你可以重用 IPython 内核机制来处理通信,而只需描述如何执行你的代码。如果目标语言可以由 Python 驱动,这就简单多了。详情见 制作简单的 Python 包装器内核

  2. 你可以用你的目标语言实现内核机制。这在最初是比较麻烦的,但是如果使用你的内核的人是用他们熟悉的语言的话,他们可能会更愿意为你的内核做出贡献。

  3. 你可以使用 xeus 库,它是 Jupyter 内核协议的 C++ 实现。内核作者只需要在他们的实现中实现特定语言的逻辑(执行代码、自动补全 。。。。。。)。如果你的目标语言可以从 C 或 C++ 驱动,这是最简单的解决方案: 例如,如果它像大多数脚本语言一样有一个 C-API。请查看 xeus 文档 以了解更多细节。基于 xeus 的内核的例子包括:

连接文件

你的内核在启动时将得到一个连接文件的路径(参见 内核规范 了解如何为你的内核指定命令行参数)。这个文件只有当前用户可以访问,它将包含一个 JSON 字典,看起来像这样

{
  "control_port": 50160,
  "shell_port": 57503,
  "transport": "tcp",
  "signature_scheme": "hmac-sha256",
  "stdin_port": 52597,
  "hb_port": 42540,
  "ip": "127.0.0.1",
  "iopub_port": 40885,
  "key": "a0436f6c-1916-498b-8eb9-e81ab9368e84"
}

transportip 和五个 _port 字段指定了五个端口,内核应该使用 ZeroMQ 绑定。例如,在上面的例子中,shell 套接字的地址是

tcp://127.0.0.1:57503

对于每个启动的内核,新的端口是随机选择的。

signature_schemekey 是用来对信息进行加密签名的,这样系统中的其他用户就不能发送代码到这个内核中运行。请参阅 Wire 协议,了解如何计算这个签名的细节。

处理消息

在读取连接文件并绑定到必要的套接字之后,内核应该进入一个事件循环,监听 hb(heartbeat)、control 和 shell 套接字。

Heartbeat 消息应立即在同一套接字上回显 —— 前端使用它来检查内核是否仍然存在。

control 和 shell 套接字上的信息应该被解析,并验证其签名。请参阅 Wire 协议 了解如何做到这一点。

内核将在 iopub 套接字上发送消息以显示输出,并在 stdin 套接字上发送消息以提示用户输入文本。

参见

Jupyter 中的消息传递

不同的套接字和通过它们传递的消息的细节

为 IPython 创建语言内核

IHaskell 的作者的博文,一个 Haskell 内核

simple_kernel

用 Python 实现内核机制的一个简单例子

内核规范

一个内核通过创建一个目录来向 IPython 标识自己,该目录的名称被用作内核的标识符。这些目录可以在很多地方创建:

Unix

Windows

系统

/usr/share/jupyter/kernels

/usr/local/share/jupyter/kernels

%PROGRAMDATA%\jupyter\kernels

环境

{sys.prefix}/share/jupyter/kernels

用户

~/.local/share/jupyter/kernels (Linux)

~/Library/Jupyter/kernels (Mac)

%APPDATA%\jupyter\kernels

用户位置优先于系统位置,名字的大小写被忽略,所以无论文件系统是否区分大小写,选择内核的方式都是一样的。由于 kernelspecs 出现在 URL 和其他地方,kernelspec 被要求有一个简单的名字,只包含 ASCII 字母、ASCII 数字和简单的分隔符:- 连字符,. 句点,_ 下划线。

如果设置了 JUPYTER_PATH 环境变量,也可以搜索其他位置。

在内核目录内,目前有三种类型的文件被使用: kernel.jsonkernel.js 和 logo 图片文件。目前,没有使用其他文件,但将来可能会改变。

在该目录内,最重要的文件是 kernel.json。这应该是一个 JSON 序列化的字典,包含以下键和值:

  • argv:一个用于启动内核的命令行参数列表。任何参数中的文本 {connection_file} 将被替换成连接文件的路径。

  • display_name: 内核的名字,它理应显示在 UI 上。与 API 中使用的内核名称不同,它可以包含任意的 unicode 字符。

  • language:内核的语言名称。当加载笔记本时,如果没有找到匹配的 kernelspec 键(可能在不同的机器上有所不同),将使用具有匹配的 language 的内核。这允许在任何 Python 或 Julia 内核上编写的笔记本与用户的 Python 或 Julia 内核正确关联,即使它们与作者的内核不在同一名称下。

  • interrupt_mode (可选):可以是 signalmessage,指定客户如何中断该内核上的单元执行,可以通过操作系统的信号设施(例如 POSIX 系统上的 SIGINT)发送一个中断 signal,或者在控制通道上发送一个 interrupt_request 消息(见 Kernel interrupt)。如果没有指定,客户端将默认为 signal 模式”

  • env (可选):一个为内核设置的环境变量字典。这些将在内核启动前被添加到当前的环境变量中。现有的环境变量可以用 ${<ENV_VAR>} 来引用,并将被替换成相应的值。管理员应该注意,使用 ${<ENV_VAR>} 可能会暴露敏感变量,应该只在受控情况下使用。

  • metadata (可选):关于这个内核的额外属性的字典;被客户用来帮助选择内核。在这里添加的元数据应该为读取和写入该元数据的工具命名。

例如,IPython 的 kernel.json 文件是这样的

{
 "argv": ["python3", "-m", "IPython.kernel",
          "-f", "{connection_file}"],
 "display_name": "Python 3",
 "language": "python"
}

要查看可用的内核规格,运行

jupyter kernelspec list

用一个特定的内核来启动终端控制台或 Qt 控制台

jupyter console --kernel bash
jupyter qtconsole --kernel bash

notebook 在 ‘New’ 按钮的下拉菜单中为你提供可用的内核。