mrkeyoor.com_
Thu 06 Aug 07:39 UTC
PyPIDataupdated 06 Aug 2026

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.

Verdict

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.

API stability3/5The CLI and kernel.json format have been stable for years, but 7.0 was shipped as potentially backward incompatible for downstream code, dropped Python 3.9, and turned base handler methods async. Front-end users feel none of that; library authors do.
Docs2/5ipykernel.readthedocs.io covers the protocol implementation and a few how-tos, and the README is mostly contributor instructions. The knowledge you need day to day, kernelspec placement, --user versus --sys-prefix, event loop pitfalls, lives in Jupyter's docs and in issue threads.
Maintenance5/5Part of the IPython organization, pushed August 2026, with releases roughly every couple of months and a detailed per-release changelog. The 282 open issues (305 counting PRs) reflect a project everything depends on rather than neglect.
Ecosystem5/5Around 19.1 million weekly downloads because JupyterLab, VS Code notebooks, papermill, nbclient and most data platforms install it transitively. Only 734 GitHub stars, which is a fair reminder that nobody stars the layer underneath the thing they use.

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
Skip it if

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.json

The 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/myenv

pip 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>.json

embed_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

PackageRegistryPick it when
ipythonPyPIYou want the enhanced REPL and magics in a terminal and never open a notebook, so the ZeroMQ and debugger layers are dead weight.
xeus-pythonPyPIYou want a Python kernel built on the C++ xeus protocol implementation, typically for lighter startup or for embedding in a different host.
jupyterlabPyPIYou just want notebooks to work: installing it brings ipykernel along and registers a default kernelspec so you never touch the CLI.