An Emacs library that allows Org mode to evaluate code blocks using a Jupyter kernel (Python by default).
Before installing, you’ll need Jupyter (>= 1.0) and IPython (>=
5.0) installed and working. You will also need the Jupyter console
and client (jupyter_console
, jupyter_client
) libraries. All of
this should be trivially installable using pip.
This package is in MELPA. I recommend installing from there.
Otherwise, for manual installation, you’ll need the following elisp dependencies first:
- https://github.com/magnars/dash.el
- Including dash-functional
- https://github.com/magnars/s.el
- https://github.com/rejeep/f.el
Then just drop this somewhere in your load path and (require
'ob-ipython)
.
Lastly, activate ipython
in Org-Babel:
(org-babel-do-load-languages
'org-babel-load-languages
'((ipython . t)
;; other languages..
))
Open an org file, add a SRC block and evaluate as you would any Org
SRC block (usually C-c C-c
). Here I will run through some example
blocks.
This is the most basic ipython block. You must provide a session argument. You can name the session if you wish to separate state. You can also pass a connection json of an existing ipython session as a session name in order to connect to it.
The result returned by ob-ipython should be renderable by org so
it’s recommended to always use :results raw drawer
.
#+BEGIN_SRC ipython :session :results raw drawer
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
#+END_SRC
Here we evaluate some code with a function definition using a named session.
#+BEGIN_SRC ipython :session mysession :exports both :results raw drawer
def foo(x):
return x + 9
[foo(x) + 7 for x in range(7)]
#+END_SRC
#+RESULTS:
: [16, 17, 18, 19, 20, 21, 22]
To get a graphic out, you must ensure that you have evaluated
%matplotlib inline
first. A file will be generated for you (see
the ob-ipython-resources-dir
custom var if you want to change the
path).
#+BEGIN_SRC ipython :session :exports both :results raw drawer
plt.hist(np.random.randn(20000), bins=200)
#+END_SRC
If you provide an ipyfile argument, this filename will be used instead of generating one.
#+BEGIN_SRC ipython :session :ipyfile /tmp/image.png :exports both :results raw drawer
plt.hist(np.random.randn(20000), bins=200)
#+END_SRC
In order to make an svg graphic rather than a png, you may specify the output format globally to IPython.
%config InlineBackend.figure_format = 'svg'
If you wish to use a specific Jupyter kernel, you may pass the kernel option. This enables you to use ob-ipython with languages other than Python. You need to have the Jupyter kernel installed and working before you can use this.
When mixing code from different languages you will need to make use of the session argument.
#+BEGIN_SRC ipython :session :kernel clojure
(+ 1 2)
#+END_SRC
#+RESULTS:
: 3
ob-ipython supports providing variables and even tables to code.
#+TBLNAME: data_table
| a | 1 | 2 |
| b | 2 | 3 |
| c | 3 | 4 |
#+BEGIN_SRC ipython :session :exports both :var x=2 :var data=data_table
(x, data)
#+END_SRC
#+RESULTS:
: (2, [['a', 1, 2], ['b', 2, 3], ['c', 3, 4]])
Asynchronous execution is supported. Use the :async t
option.
#+BEGIN_SRC ipython :session :ipyfile /tmp/image.png :exports both :async t :results raw drawer
import time
time.sleep(3)
plt.hist(np.random.randn(20000), bins=200)
#+END_SRC
This package is starting to transition from the original ipython-only support to full jupyter support.
If you have other kernels installed, you should be able to evaluate blocks by providing jupyter-X as the language, where X is the language name recognised by jupyter. For example, you can do something like this:
#+BEGIN_SRC jupyter-R :results raw drawer
x <- 3
x
#+END_SRC
Notice, when providing languages like this, you do not need to (although you may) provide a session argument. A default session is created per language. This should also try to provide support for per-language modes when editing.
First, follow the instructions here to get access to a remote kernel. You can then pass the name of the local json file as a session arg to use this tunnel.
Essentially the instructions boil down to
- scp the remote json file over (will be in
/run/user/*/jupyter/
) - use
jupyter console --existing ./kernel-remote.json --ssh remote-host
to connect locally. This will give you a prompt that should allow you to evaluate expressions locally but have them run on the remote machine. - use the newly created local config file printed by the previous step
as the argument to
:session
. Be sure to include the.json
suffix so that ob-ipython knows to use this as the config rather than spawning a new session.
- You can ask the running IPython kernel for documentation. Open a
SRC block, place the point on the thing you’re interested in and
run
M-x ob-ipython-inspect
. I recommend you bind this to a key. ob-ipython-completions
queries the kernel for completions at a position. You may use this to hook up any completion mechanism. We already provide a company backend. With company installed, add(add-to-list 'company-backends 'company-ob-ipython)
somewhere in your config. This should then work while editing a src block.- It’s often easier to play with code using a REPL. With the point
in an ipython SRC block, you can open a REPL connected to the
current kernel by running
C-c C-v C-z
. - If evaluated code produces an error, this will be displayed nicely in a buffer using IPython’s traceback support.
- Stdout/err from code evaluation is displayed in a popup buffer.
This is great for debugging or getting verbose output that is
best left out of documents (e.g progress updates). If you wish to
capture output in your document use the
:results output
SRC block header. - You can interrupt or kill a running kernel. This is helpful if
things get stuck or really broken. See
M-x ob-ipython-interrupt-kernel
andM-x ob-ipython-kill-kernel
, respectively.
Here are a few things I have setup to make life better. These aren’t provided with ob-ipython, but are recommended.
- Be sure to use
%matplotlib inline
, otherwise graphics won’t work. - I use yasnippet to create src blocks. Here is the snippet I use.
It takes care of generating unique file names (when I want one)
so I don’t have to think about this.
# -*- mode: snippet -*- # name: ipython block # key: py # -- #+BEGIN_SRC ipython :session ${1::ipyfile ${2:$$(let ((temporary-file-directory "./")) (make-temp-file "py" nil ".png"))} }:exports ${3:both} $0 #+END_SRC
These days I use this more often:
#+BEGIN_SRC ipython :session :exports both :results raw drawer $0 #+END_SRC
- I use the following Org settings:
(setq org-confirm-babel-evaluate nil) ;don't prompt me to confirm everytime I want to evaluate a block ;;; display/update images in the buffer after I evaluate (add-hook 'org-babel-after-execute-hook 'org-display-inline-images 'append)
- Export with the
LaTeX
backend using theminted
package for source block highlighting fails foripython
blocks by default with the errorError: no lexer for alias 'ipython' found
To use the
python
lexer foripython
blocks, add this setting:(add-to-list 'org-latex-minted-langs '(ipython "python"))
- ob-ipython will display anything back from ipython with the mime
type ‘text/org’ verbatim. This allows you and others to create
formatters that output raw org. For example, drop this in your
ipython startup file to have arrays and dataframes rendered as
org tables:
import IPython from tabulate import tabulate class OrgFormatter(IPython.core.formatters.BaseFormatter): def __call__(self, obj): try: return tabulate(obj, headers='keys', tablefmt='orgtbl', showindex='always') except: return None ip = get_ipython() ip.display_formatter.formatters['text/org'] = OrgFormatter()
First thing to do is check that you have all of the required dependencies. Several common problems have been resolved in the project’s issues, so take a look there to see if your problem has a quick fix. Otherwise feel free to cut an issue - I’ll do my best to help.
I tried using the IPython notebook but quickly became frustrated with trying to write code in a web browser. This provides another option for creating documents containing executable Python code, but in Emacs - with everything that entails.
Why not use EIN?
EIN is really great. It kept me happy for quite a while but I started to feel constrained by the cell format of IPython notebooks. What I really wanted was to embed code in Org documents. It’s hard to compete with Org mode! A few key points in favour of Org:
- In my opinion, Org’s markup is better than Markdown.
- Org’s organisational, editing and navigation facilities are much better than EIN.
- Org’s tables…
- Org can export to multiple formats.
- I like how Org opens a new buffer when editing code so that you can use a Python major mode rather than trying to handle multiple major modes in one.
I also found myself hitting bugs in EIN where evaluation and doc lookup would just stop working. I regularly had to kill and reopen buffers or restart the IPython kernel and this was getting frustrating.
I think this is more robust. The executed code is sent to a running IPython kernel which has an architecture designed for this purpose. The way ob-python works feels like a bit of a hack. I ran in to race conditions using ob-python where the Org buffer would update its results before the Python REPL had finished evaluating the code block. This is what eventually drove me to write this.
It’s easier to get plots and images out of this. I also provide several features I missed when using plain ob-python, such as looking up documentation and getting IPython-style tracebacks when things go wrong.
You can also use IPython-specific features such as %timeit
.