A Superior Alternative to Jupyter Notebooks


[← Back]

The problems with .ipynb

The fact that Jupyter notebooks are not just simple text files creates some inconveniences. The main ones being:

Even though Jupyter Notebooks can be opened with a text editor, they can’t really be edited that way since they are full of text that is just JSON jumble. So most of the text you end up seeing is not the Python code you want to edit. Consequently, in git diffs most of the “changes” that show up are actually just metadata.

The alternative

The solution I found and that I’ve been using for the past year is a Neovim plugin called iron.nvim. This plugin basically allows you to turn a simple .py files into notebooks simply by adding delimiters #%% into the Python code which serve as separators between cells. So everything between two #%% is considered a single cell and works just as you would expect in a Jupyter notebook.

Configuration

The official repository outlines the basic configuration needed to have iron running. However, there are some tweaks I made that are worth sharing.

REPL Window

I like to have the REPL window to be vertical and take 40% of the space.

repl_open_cmd = view.split.vertical.botright(vim.api.nvim_win_get_width(0) * 0.4)

Cell Operations

These keymaps allow you to navigate, add, and delete cells quickly.

-- Go to cell below
vim.keymap.set("n", "<space>j" , "/#%%\\|# %%<Enter><CR>:nohlsearch<CR>", {silent = true})

-- Go to cell above
vim.keymap.set("n", "<space>k" , "?#%%\\|# %%<CR>n:nohlsearch<CR><Enter>", {silent = true})

-- Add cell below
vim.keymap.set("n", "<space>aj", "/#%%\\|# %%<CR>:nohlsearch<CR>O#%%<Enter><Esc>O", {silent = true})

-- Add cell above
vim.keymap.set("n", "<space>ak", "?#%%\\|# %%<CR>:nohlsearch<CR>O#%%<Enter><Esc>O", {silent = true})

-- Delete cell
vim.keymap.set("n", "<space>dd", "?#%%\\|# %%<enter>v/#%%\\|# %%<enter>kd<CR>:nohlsearch<CR>", {silent = true})

-- Add separator symbol
vim.keymap.set("n", "<space>ah", "o<Esc>i#%%<enter>")

Autodetect Virtual Environment

This allows Iron to detect the virtual environment.

-- Helper to find the current project's venv
local function get_venv_python()
  local venv = vim.fn.getcwd() .. "/.venv/bin/python3"
  if vim.fn.executable(venv) == 1 then
	return venv
  else
	return "python3"  -- fallback to system python3
  end
end

If you prefer to use ipython you can simply replace it like this:

-- Helper to find the current project's venv
local function get_venv_python()
  local venv = vim.fn.getcwd() .. "/.venv/bin/ipython3"
  if vim.fn.executable(venv) == 1 then
	return venv
  else
	return "ipython3"  -- fallback to system ipython3
  end
end