Making kernels for jupyter
一个内核是运行和解析用户代码的程序。IPython包含了一个运行和解析Python代码的内核,而且人们已经写了多种语言的内核。
当Jupyter开始一个内核的时候,它会传递它一个连接文件。它指定了如何与前端开始通信。
以下是实践:
安装环境
conda create -n py365400 python=3.6.5 jupyter ipykernel |
列出当前内核
在Unix系统中,可用的内核列在如下文件夹中(Kernel specs):
System:
/usr/share/jupyter/kernels
/usr/local/share/jupyter/kernels
Env:
{sys.prefix}/share/jupyter/kernels
User:
~/.local/share/jupyter/kernels (Linux)
~/Library/Jupyter/kernels (Mac)
用户位置的优先级高于系统级别的,忽略名字的大小写。因此不论系统是否大小写敏感,都可以以同样的烦噶事来获取内核。因为内核名字会在URL出现,因此内核名字需要是一个简单的,只使用ASCII字母,数字和简单的分隔符-
,.
, _
。
如果设置了 JUPYTER_PATH
环境变量的话,也会搜索其他位置。
例如在我的Mac上,有两个个内核,一个是 python 3 的,另一个是 pyspark(python 2) 的。
jupyter kernelspec list |
在内核文件夹下,现在会使用三种类型的文件。kernel.json
, kernel.js
和log图片文件。目前,没有使用其他文件,但是将来可能会改变。
最重要的文件是 kernel.json
,应该是一个json序列化的字典包含以下字段
argv
: 用来启动内核的命令行参数列表。{connection_file}
将会被实际的连接文件的路径替换。display_name
: 在UI上展示的内核名字。不像在API中使用的内核名字,这里的名字可以包含任意字符。language
: 内核的语言名字。当载入notebook的时候,如果没有找到匹配的内核,那么匹配相应语言的内核将会被启动。这样允许一个写了任何Python或者julia内核的notebook可以与用户的Python或者julia内核合适的联系起来,即使它们没有在与用户内核同样的名字下。interrupt_mode
:可能是signal或者message指定了客户端如何在这个内核中停止单元运行。是通过发送一个信号呢,还是发送一个interrupt_request
消息在control channel
。如果没有指定,将默认使用signal模式。env
:为内核设置的环境变量。在内核启动前,会添加到当前的环境变量里。metadata
:关于这个内核的其他相关属性。帮助客户端选择内核。
比如:
cat /usr/local/miniconda3/envs/py365/share/jupyter/kernels/python3/kernel.json |
当内核开始的时候将会传入一个连接文件的路径,这个文件只对当前用户可用,会包含类似下面的一个JSON字典。
{ |
transport
, ip
和设定了该使用 ZeroMQ 绑定的五个_port。比如 shell 套接字的地址应该是:tcp://127.0.0.1:57503
。在每个内核开始的时候会指定随意的端口。signature_scheme
和 key
用来加密信息,因此系统的其他用户不能发送代码来运行内核。
现在我需要自己定义一个内核,这个内核可以执行我们定义的逻辑。
添加新内核
这是简单的重用 IPython 的内核机制来实现这个新的内核。
步骤:
子类化ipykernel.kernelbase.Kernel,然后实现下面的方法和属性
class MyKernel
- implementation
- implementation_version
- banner
Kernel info会返回的信息。Implementation指的是内核而不是语言,比如IPython而不是Python。banner是在控制UI上显示第一个提示符之前的东西。这些都是字符串
- language_info
Kernel info会返回的信息字典。应该包含mimetype键,值是目标语言的mimetype,比如text/x-python。name键是实现的语言比如python,file_extension比如.py,而且也可能根据不同语言包含codemirror_mode和pygments_lexer
- do_execute(code, silent, store_history=True, user_expressions=None, allow_stdin=False)
执行用户代码
- code:要执行的代码
- silent:是否展示输出
- store_history: 是否在历史里记录代码,并且增加执行次数。
- user_expressions:在代码被执行后对这些表达式求值
- allow_stdin:前端是否提供输入请求
你的方法应该返回一个字典,包含在Execution results规定的字典。为了展现输出,它可以使用send_response() 来发送消息。
为了启动你的内核,在模块后面加上:
if __name__ == '__main__': |
现在创建一个JSON的内核说明文件,然后通过 jupyter kernelspec install </path/to/kernel>
。将你的内核模块放在Python可以导入的地方,一般是当前目录(做测试)。最后,你可以使用 jupyter console --kernel <mykernelname>
来运行你的内核。
例子:
ls echo/ |
echokernel.py
:
from ipykernel.kernelbase import Kernel |
kernel.json
:
{ |
安装
$ jupyter kernelspec install echo --user |
这里,只为当前用户添加这个kernel。
查看
jupyter notebook |
选择新创建的内核创建 notebook,并运行代码。
一些坑
- 运行 notebook 时无法找到
echokernel
模块:
[I 15:48:27.754 NotebookApp] Kernel started: 77759cfa-db55-4b70-be23-c14d69f8d87d |
需要将 echokernel.py
放置在 python PATH 中 ,这样在执行命令时才能访问到。
更多命令
$ jupyter kernelspec help |
删除内核
jupyter kernelspec uninstall echo |
参考
- Making simple Python wrapper kernels
- 题 如何将python3内核添加到jupyter(IPython)
- 翻译 - Making kernels for Jupyter