功能简介

  1. 提供一个 emacs 内部界面友好的 ipython 命令行(自动补全,帮助弹出)
  2. 提供 org babel 支持,类似 ob-ipython

依赖

默认 python 环境要安装 jupyter

1
sudo python -m pip install jupyter -i https://pypi.douban.com/simple

pycse

scimax jupyter, 用来支持 jupyter 到 org-mode 数据转换

启动

启动一个 ipython 命令行入口

M-x jupyter-run-repl

连接已有的 ipython 命令行

M-x jupyter-connect-repl

  • 注意: 需要用到 ipython 连接文件

什么是 connection file: 参见: Making kernels for Jupyter — jupyter_client 7.1.0 documentation

自动补全

C-M-i 手动触发,tab 键无效

帮助

M-i

历史

前后

M-pM-n

查找

C-sC-r

执行

类似 emacs 对 elisp 的快捷键, 以 C-c 为前缀 参考: GitHub - nnicandro/emacs-jupyter: An interface to communicate with Jupyter ke…

类似 ob-ipython 功能

参考: GitHub - nnicandro/emacs-jupyter: An interface to communicate with Jupyter ke… eg:

1
print("hello")
hello

headers:

  • :session
  • :kernel

    • 指定 python 版本等
  • :results

    • raw

      • 避免转换城 begin_export 对于生成的 latex 结果
    • scalar

      • 生成的 列表 转换城 org mode plain list, 不是 python 列表

        1
        
        print([1,2,3])
        [1, 2, 3]
        
    • output

      • 对于 pd.DataFrame, 使用 :results output 可以正常显示

        • 待议,有时不正常
        • output 不可,但是 value/raw/scalar 都可以
  • :display

    • 指定输出的 mime type
    • 默认值: text/html
    • 取值

      • eg: :display text/plain text/html plain 比 html 优先级高
      • display plain plain text
  • :file

    • 用于指定图片文件的位置

      • 默认图片位置 org-babel-jpyter-resource-directory
      • 默认名称,随机生成
  • :pandoc

    • 格式问题
  • :async

    • 异步执行问题,使用 ob-async
    • 取值: :async yes

使用 jupyter 替代 ob-python

代码: (org-babel-jupyter-override-src-block "python") 参考: GitHub - nnicandro/emacs-jupyter: An interface to communicate with Jupyter ke…

远程连接

通过 :session header 使用 tramp /ssh:host:/path/to/jupyter-connection-file.json 语法实现 例子:

1
...

通过 tramp 打开 远程 repl

语法: M-x dired RET /jpy:localhost#8888:/

FAQ

报 json-readable-error 错误

参考: nnicandro/emacs-jupyter#301 Emacs 27 json-read: JSON readtable error: 76 原因分析: 当前 python 环境没有安装 jupyter

解决方案:

1
pip install jupyter

报错 No org-babel-execute function for jupyter-python

参考: nnicandro/emacs-jupyter#306 No org-babel-execute function for jupyter-python 原因分析: 没有成功加载 ob-jupyter

解决方案:

1
(require 'ob-jupyter)

org-babel-jupyter-session-key: Need a valid session and a kernel to form a key

原因分析:未能启动 jupyter 后台

解决方案:

  • revert-buffer
  • 注释掉 session id

    #+PROPERTY: header-args:jupyter-python  :session jupyter-python-ae43ef8be0720e6d8693d0b25a77ef13
    
    • 把这一行注释掉

切换 virtual env

手动刷新法

参考:emacs-jupyter/community - Gitter 步骤:

  1. emacs 内部切换 virtualenv

    • M-x conda-env-activate
  2. 手动刷新 jupyter kernel

    1
    
    (jupyter-available-kernelspecs 'refresh)

使用 ipykernel install 实现

参考:

  1. 官方:https://ipython.readthedocs.io/en/stable/install/kernel_install.html
  2. 详细使用:Using Multiple Python Versions and Environments with RStudio Workbench and Ju…

步骤:

  1. root python (系统 python)下安装 ipykernel

    1
    
    python -m pip install ipykernel
  2. 创建虚拟环境(已有可以跳过)

    1
    
    conda create -n ml37 python=3.7
  3. 激活环境

    1
    
    conda activate ml37
  4. 注册 kernel

    1
    2
    3
    4
    5
    6
    7
    8
    
    # * system系统, 安装配置位置: /usr/loca/share/jupyter/kernels/...
    python -m ipykernel install --name ml37 --display-name "Python3 (ml37)"
    
    # * user 当前用户, 安装配置位置: ~/.local/share/jupyter/kernels/...
    python -m ipykernel install --user --name ml37 --display-name "Python3 (ml37)"
    
    # * 安装到 conda base 中
    python -m ipykernel install --prefix="d:/soft/miniconda3/" --name ml37 --display-name "Python3 (ml37)"

doom emacs

方法一

步骤:

  1. 在 shell: conda env activate my-env
  2. 修改 doom 环境变量文件 env: doom sync
  3. 启动 doom emacs

org babel jupyter 设置 kernel

通过 src block :kernel your-python-kernel 切换 kernel, 进而切换 python 版本和 virtual env

不设置 :kernel

1
2
3
import sys

sys.prefix

/usr

设置 :kernel

1
2
3
import sys

sys.prefix

/home/sawyer/miniconda3/envs/ml38

设置 默认 session 和 默认 kernel

#+PROPERTY: header-args:jupyter-python  :session jupyter-python-ae43ef8be0720e6d8693d0b25a77ef13 :kernel chem38

快捷方法:

  • 通过 yasnippt 实现, 键 <jps

注意

org-mode file 级别的设置

  • 必须 revert-buffer 才能生效
  • 不然,可能导致 执行代码块错误,因为探知不到正确的 :session 配置 :kernel 配置

图片展示

通过 IPython.display 模块实现不支持的绘图工具结果展示

1
2
3
from IPython.display import Image

Image("path/to/myfile.png")

使用 IPython.display.Image 包裹

  • 核心代码

    1
    2
    3
    4
    5
    
    from IPython.display import Image
    filename = "./bokeh_line_square.png"
    export_png(p, filename=filename) # p 是 boken 的Figure 对象
    
    Image(filename) # 在 org-mode 文件内部,生成 [[./path/to/mypiture.png]]
  • 已测试例子:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    import numpy as np
    from bokeh.plotting import figure, show
    from bokeh.io import output_file, export_png
    from IPython.display import Image
    
    x = np.linspace(1, 10, 10)
    y = x**2
    p = figure(title="first figure y=x^2", x_axis_label="x", y_axis_label="y")
    square = p.line(x, y)
    
    # show(p) # 外部打开浏览其展示临时文件:html
    filename = "./bokeh_line_square.png"
    export_png(p, filename=filename)
    
    Image(filename) # 在 org-mode 文件内部,生成 [[./path/to/mypiture.png]]

使用 bokeh 制作 image object

中间 不生成 图片文件,而是使用图片对象

  • 核心代码

    1
    2
    3
    
    from bokeh.io.export import get_screenshot_as_png
    
    get_screenshot_as_png(p)
  • 完整代码

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    import numpy as np
    from bokeh.plotting import figure, show
    from bokeh.io import output_file, export_png
    from IPython.display import Image
    from bokeh.io.export import get_screenshot_as_png
    x = np.linspace(1, 10, 10)
    y = x**2
    p = figure(title="first figure y=x^2", x_axis_label="x", y_axis_label="y")
    square = p.line(x, y)
    
    # show(p) # 外部打开浏览其展示临时文件:html
    get_screenshot_as_png(p)

制作显示工具

  • 工具代码

    1
    2
    3
    4
    5
    
    from bokeh.io.export import get_screenshot_as_png
    
    
    def showobj(fig: "Figure", *args, **kwargs):
        return get_screenshot_as_png(fig, *args, **kwargs)
  • 用法

    1
    
    showobj(p)                      # p 是 bokeh 的外部对象

plotly

支持更好,使用 pycse 实现

依赖安装:

1
2
pip install pycse
pip install -U kaleido

plotly 示范代码:

1
2
3
4
5
6
7
from pycse.plotly import *

import plotly.express as px
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species",
                 size='petal_length', hover_data=['petal_width'])
fig.show()

exception backtrace 错误代码跳转

由 scimax-jupyter 提供

M-x scimax-jupyter-org-hydra/body 或者 <F12> e 或者 C-c C-h e

注意: 必须在代码块内部才能正确跳转

repl 和 scratch

jupyter repl

  • org-mode src block –> repl

    • scimax-jupyter: <F12> r
    • org babel core: C-c C-v C-z
  • jupyter scratch –> repl

    • C-c C-z

jupyter scratch

两种方式

  • jupyter repl buffer –> scratch

    • C-c C-sM-x jupyter-repl-scratch-buffer
  • org-mode src block —> scatch

    • scimax-jupyter: org-mode python 代码块内部
    • <F12> s
    • 有 bug, 不能用

src block as scratch

  • 当前代码块执行 defun

    • C-M-xM-x jupyter-eval-defun
  • 行执行或选定区域执行 line || region

    • C-x C-eM-x jupyter-eval-line-or-region

debug

scimax 官方方法

步骤

  1. 定义代码,使用 breakpoint() 设置断点

    1
    2
    3
    4
    
    def hello(x):
        3 % x
        breakpoint()
        return 1/x
  2. 执行 src block
  3. 跳转到 repl F12 z
  4. 执行代码,自动 进入 debug 状态

问题:

  • 这种方法无效

ipython magic 方法

  • 在 repl 中
  • 使用 %debug || %%debug 包裹代码
  • 调用 pdb 调试

emacs-jupyter + org-mode

如何设置 :kernel

当不指定 kernel 时

  1. load library

    (require 'jupyter)
    (require 'ob-jupyter)
    
  2. 打开 .org 文件
  3. 输入内容:

    • 注意下面的例子,没有设置 :kernel

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      #+PROPERTY: header-args:jupyter-python :session demo 
      #+PROPERTY: header-args:jupyter-python+ :async yes
      
      
      #+begin_src jupyter-python
      import sys
      sys.prefix
      
      print(sys.prefix)
      #+end_src
      
      #+RESULTS:
      : /home/sawyer/miniconda3/envs/llm311
  4. 连接 jupyter repl

    • jupyter-connect-server-repl : 连接 jupyter 命令启动的 server
    • jupyter-connect-repl : 需要使用 connect 文件
  5. 编辑和执行

    • C-c h : jupyter-org-hydra/body : jupyter hydra 命令用来执行代码块的​~运行~ 、 编辑跳转

指定 :kernel

  1. load library

    (require 'jupyter)
    (require 'ob-jupyter)
    
  2. 创建 kernel(第一次连接 juyter)

    • jupyter-server-launch-kernel
    • 如果已经创建过 kernel, 跳过这个步骤

      • 包括之前在 emacs 或者在 jupyter notebook/lab 上手动运行的 .ipynb
  3. 连接 jupyter repl(已经连接jupyter 之后)

    • jupyter-connect-server-repl : 连接 jupyter 命令启动的 server
    • jupyter-connect-repl : 需要使用 connect 文件
  4. 在运行的 kernel 列表

    • jupyter-server-list-kernels
  5. 重命名 kernel

    • jupyter-server-list-kernels 命令打开的界面上
    • 这里启动了 jupyter-server-kernel-list-mode

      • 提供的功能: jupyter-server-kernel-list-mode-map

         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        
        (defvar jupyter-server-kernel-list-mode-map
          (let ((map (make-sparse-keymap)))
            (define-key map (kbd "C-c C-i") #'jupyter-server-kernel-list-do-interrupt)
            (define-key map (kbd "d") #'jupyter-server-kernel-list-do-shutdown)
            (define-key map (kbd "C-c C-d") #'jupyter-server-kernel-list-do-shutdown)
            (define-key map (kbd "C-c C-r") #'jupyter-server-kernel-list-do-restart)
            (define-key map [follow-link] nil) ;; allows mouse-1 to be activated
            (define-key map [mouse-1] #'jupyter-server-kernel-list-new-repl)
            (define-key map (kbd "RET") #'jupyter-server-kernel-list-new-repl)
            (define-key map (kbd "C-RET") #'jupyter-server-kernel-list-launch-kernel)
            (define-key map (kbd "C-<return>") #'jupyter-server-kernel-list-launch-kernel)
            (define-key map (kbd "<return>") #'jupyter-server-kernel-list-new-repl)
            (define-key map "R" #'jupyter-server-kernel-list-name-kernel)
            (define-key map "r" #'revert-buffer)
            (define-key map "g" #'revert-buffer)
            map))
      • R: rename
      • d: shutdown
      • RET: create new kernel
      • C-c C-i: interrupt
      • C-c C-r: restart
      • C-c C-d: shutdown
  6. kernel 的默认名称

    • kernel 的 uuid
    • 当前 jupyter notebook 启动使用的 kernel 的默认名称 python3
  7. 编辑 .org 文件(org-mode)

    • 使用 :kernel 指定 kernel

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      
      #+PROPERTY: header-args:jupyter-python :session demo :kernel python3
      #+PROPERTY: header-args:jupyter-python+ :async yes
      
      #+begin_src jupyter-python
      import sys
      sys.prefix
      
      print(sys.prefix)
      #+end_src
      
      #+RESULTS:
      : /home/sawyer/miniconda3/envs/py311

repl 使用

步骤:

  1. 关联或创建 repl:

    • 关联到已有的buffer: jupyter-repl-associate-buffer
    • 创建 buffer: jupyter-connect-server-repl

      • 特点:可以选择 kernel 类型(对应 venv 环境)
  2. 跳转到 repl buffer: jupyter-repl-pop-to-buffer
  3. 打开一个草稿 repl buffer(scratch): jupyter-repl-scratch-buffer

使用 connection file + org-mode

使用这个麻烦方法的原因:

  • 目标的测试结果表明, remote kernel + jupyter-repl-associate-buffer 只可以关联 .py 文件 buffer 和 remote repl
  • 但是如果使用 jupyter-repl-associate-buffer 关联一个 org-mode, jupyter 只会尝试启动一个本地的 jupyter

方法:

  1. 准备 kernel 和 connection file

    1. 使用 jupyter-connect-server-repljupyter-server-launch-kernel 等命令创建一个 kernel

      • 可以查看到 kernel 对应的 id
    2. 在 remote ssh host 上查看对应的 connect file 文件路径

      • 潜在存放路径: /home/<user>/.local/share/jupyter/runtime/kernel-<id>.json
      • 如果不正确,可以通过命令 jupyter --runtime-dir, 获取存储文件夹
  2. 设置公共的header

    #+PROPERTY: header-args:jupyter-python  :session /sshfs:gpu:/home/sawyer/.local/share/jupyter/runtime/kernel-108e82e6-b11f-43d5-8b60-f136ff04f249.json
    
    • 通过 ssh 指定使用的 kernel 的连接文件的路径
  3. 启动方法:

    • 重新打开文件或者在上面的指令上面执行 Ctrl+c Ctrl+c
  4. 正常使用 org-babel 的命令执行代码

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#+PROPERTY: header-args:jupyter-python  :session /sshfs:gpu:/home/sawyer/.local/share/jupyter/runtime/kernel-108e82e6-b11f-43d5-8b60-f136ff04f249.json


* head
#+begin_src jupyter-python

import sys
print(sys.path)

from pathlib import Path
print(Path.cwd())
#+end_src

#+RESULTS:
: ['/home/sawyer/.config', '/home/sawyer/miniconda3/envs/py311/lib/python311.zip', '/home/sawyer/miniconda3/envs/py311/lib/python3.11', '/home/sawyer/miniconda3/envs/py311/lib/python3.11/lib-dynload', '', '/home/sawyer/miniconda3/envs/py311/lib/python3.11/site-packages']
: /home/sawyer/.config