ipykernel
ipykernel is the program Jupyter actually launches when you pick a Python kernel. It wraps IPython in the Jupyter messaging protocol over ZeroMQ, so a front end (JupyterLab, VS Code, nbclient, papermill, Colab) can send code to a separate Python process and get back stdout, rich display data, completions, inspection results and debugger events. You almost never import it. You install it into an environment and then register a kernelspec, a small kernel.json that records the exact interpreter path a front end should start. Which is why nearly every "my notebook cannot import the package I just pip installed" problem is really an ipykernel problem.
Not a choice so much as a fact: if anything in your stack runs Python inside Jupyter, ipykernel is already there. Learn the kernelspec CLI, because managing kernel.json well is the entire difference between reliable notebooks and a week of phantom import errors.
Use it if
- You want a specific virtualenv, venv or conda environment to show up as a kernel choice in JupyterLab or VS Code, which is what python -m ipykernel install --name does
- You execute notebooks headlessly with nbclient, papermill or nbconvert, all of which start a kernel by kernelspec name and need one installed
- You need the notebook debugger: ipykernel bundles debugpy and advertises metadata.debugger in kernel.json, which is what makes breakpoints work in JupyterLab and VS Code
- You are building a tool that speaks the Jupyter protocol and want the reference Python implementation to test against, or you want to embed a live kernel inside a long-running application so you can attach a notebook to it
- You only want an interactive Python REPL in a terminal. ipython alone gives you that; ipykernel drags in pyzmq, tornado, debugpy, jupyter-client, jupyter-core, psutil, comm, matplotlib-inline, traitlets and nest-asyncio2 on top
- You are building a slim container or a Lambda layer. The dependency tree includes compiled wheels (pyzmq, psutil, debugpy) and on musl or uncommon architectures pip will fall back to building them from source
- You are on Python 3.9 or older. ipykernel 7 requires 3.10 or newer, so you have to pin ipykernel<7 and stay on the 6.x line
- You maintain a downstream tool that reaches into kernel internals. 7.0 rearchitected message handling to support kernel subshells (JEP 91), and the maintainers say plainly that it may break assumptions in downstream libraries and to pin ipykernel<7 if it does
- You want an alternative kernel design, for example a C++ implementation with different startup and debugging behavior; that is what xeus-python is for
Setup reality
pip install ipykernel installs the package but no front end will list it until a kernelspec exists. python -m ipykernel install --sys-prefix --name myenv --display-name "Python (myenv)" writes kernel.json under the environment's share/jupyter/kernels. The failure everyone hits at least once: Jupyter is installed in environment A, ipykernel in environment B, and the kernelspec's argv records whichever interpreter ran the install command, so your notebook silently imports from a different environment than your terminal. Diagnose it with jupyter kernelspec list, read the argv path in the kernel.json, and confirm inside a cell with import sys; sys.executable. Beyond that: --user writes to your home directory and --sys-prefix writes into the environment, and picking the wrong one leaves stale kernels in the picker forever, because uninstalling the package does not remove the kernelspec. 7.x also needs Python 3.10 or newer and pulls pyzmq, tornado, debugpy and psutil wheels. Event loop integration (%gui qt, top-level await, libraries that call asyncio.run inside a cell) is the other recurring source of hangs.
Patterns
Make the current environment selectable in Jupyterregister-env-as-kernel
# from inside the activated environment
python -m pip install ipykernel
python -m ipykernel install --sys-prefix \
--name myenv --display-name "Python (myenv)"
# writes <env>/share/jupyter/kernels/myenv/kernel.json--sys-prefix keeps the kernelspec inside the environment, so deleting the env deletes the kernel. --user writes to your home directory instead and survives the env, which is how stale entries pile up in the kernel picker.
See which kernels exist and where they pointinspect-kernelspecs
jupyter kernelspec list
# Available kernels:
# python3 /path/to/env/share/jupyter/kernels/python3
# myenv /path/to/env/share/jupyter/kernels/myenv
cat /path/to/env/share/jupyter/kernels/myenv/kernel.jsonThe argv array in kernel.json is the whole story: its first element is the interpreter that will run your notebook. If that path is not the environment you think you are in, nothing else you try will fix the imports.
Prove which Python a notebook is actually usingconfirm-running-interpreter
import sys
print(sys.executable)
print(sys.prefix)
%pip install requests # installs into THIS kernel's environment
# not: !pip install requests%pip is a magic that targets the kernel's own interpreter; !pip runs whatever pip is first on the shell PATH, which is frequently a different environment. This single distinction explains most "I installed it but it is not there" reports.
Bake environment variables into a kernelkernelspec-env-vars
python -m ipykernel install --sys-prefix --name gpu-job \
--display-name "Python (GPU)" \
--env CUDA_VISIBLE_DEVICES 0 \
--env OMP_NUM_THREADS 4
# results in kernel.json:
# "env": {"CUDA_VISIBLE_DEVICES": "0", "OMP_NUM_THREADS": "4"}Values are set when the kernel process starts, so they take effect before imports; setting them with os.environ in the first cell is too late for libraries that read them at import time. Never put secrets here: kernel.json is plain text on disk.
Delete a kernel that no longer existsremove-stale-kernel
jupyter kernelspec list
jupyter kernelspec uninstall myenv
# or just remove the directory
rm -rf ~/.local/share/jupyter/kernels/myenvpip uninstall ipykernel does not remove kernelspecs. A kernelspec whose argv points at a deleted virtualenv still shows in the picker and fails with "No such file or directory" only after you try to start it.
Run a notebook from a script or CIexecute-notebook-headless
import nbformat
from nbclient import NotebookClient
nb = nbformat.read("report.ipynb", as_version=4)
client = NotebookClient(nb, timeout=600, kernel_name="myenv")
client.execute()
nbformat.write(nb, "report.executed.ipynb")kernel_name has to match an installed kernelspec name, not a display name and not a path. In CI that means the pipeline must run python -m ipykernel install before this, or you get NoSuchKernel.
Attach a notebook to a running programembed-kernel-in-app
from ipykernel.embed import embed_kernel
def debug_here(state):
# blocks; prints a connection file path to stderr
embed_kernel(local_ns={"state": state})
# then, from a shell:
# jupyter console --existing kernel-<pid>.jsonembed_kernel blocks the calling thread and exposes your process namespace over a local socket, so it is a debugging tool, not something to leave in a service. The connection file it prints holds the HMAC key that grants code execution.
Find the connection file of the current kernelread-connection-info
from ipykernel.connect import get_connection_file, get_connection_info
print(get_connection_file())
print(get_connection_info(unpack=True))
# {'shell_port': 51234, 'iopub_port': ..., 'key': '...', 'transport': 'tcp', ...}Anyone who can read that file can execute code in the kernel, because the key field signs messages. On shared machines keep the runtime directory private; ipykernel 7.3 added ZMQ Curve transport encryption as the stronger option.
Build a kernel for your own language or DSLcustom-kernel
from ipykernel.kernelbase import Kernel
class EchoKernel(Kernel):
implementation = "Echo"
implementation_version = "1.0"
banner = "Echo kernel"
language_info = {"name": "echo", "mimetype": "text/plain",
"file_extension": ".txt"}
async def do_execute(self, code, silent, store_history=True,
user_expressions=None, allow_stdin=False,
*, cell_meta=None, cell_id=None):
if not silent:
self.send_response(self.iopub_socket, "stream",
{"name": "stdout", "text": code})
return {"status": "ok", "execution_count": self.execution_count,
"payload": [], "user_expressions": {}}
if __name__ == "__main__":
from ipykernel.kernelapp import IPKernelApp
IPKernelApp.launch_instance(kernel_class=EchoKernel)In ipykernel 7 do_execute is a coroutine and gained keyword-only cell_meta and cell_id parameters. A synchronous do_execute written for 6.x still runs but you should accept **kwargs so future protocol fields do not raise TypeError.
Stay on the 6.x linepin-for-old-python
# requirements.txt / pyproject dependency
ipykernel<7
# reasons: Python 3.9, or a downstream tool that
# reads 6.x kernel internals (older Spyder, ipyparallel)7.0 dropped Python 3.9 and is documented as potentially backward incompatible for downstream libraries because of the subshell rework. Pinning is the maintainers' own recommendation, not a workaround someone invented.
Use await at the top level of a cellasync-in-cells
import asyncio
async def fetch():
await asyncio.sleep(1)
return 42
result = await fetch() # legal at cell top level
# this, however, deadlocks:
# asyncio.run(fetch())The kernel already runs an event loop, so top-level await works but asyncio.run and loop.run_until_complete raise or hang. Libraries that call asyncio.run internally are the usual cause of a cell that never finishes.
Pick a plotting or GUI event loopmatplotlib-and-gui-loops
%matplotlib inline # static PNGs, provided by matplotlib-inline
%matplotlib widget # needs ipympl installed
%gui qt # needs the pyqt5 or pyside6 extra
# pip install "ipykernel[pyqt5]"matplotlib-inline ships as an ipykernel dependency, so inline plots work out of the box; every other backend is an extra install. %gui integrates a GUI loop with the kernel loop, and mixing it with a blocking app.exec_() call will freeze the kernel.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| ipython | PyPI | You want the enhanced REPL and magics in a terminal and never open a notebook, so the ZeroMQ and debugger layers are dead weight. |
| xeus-python | PyPI | You want a Python kernel built on the C++ xeus protocol implementation, typically for lighter startup or for embedding in a different host. |
| jupyterlab | PyPI | You just want notebooks to work: installing it brings ipykernel along and registers a default kernelspec so you never touch the CLI. |