Skip to content

nanopython

Python where CPython doesn't fit. A browser tab. A small Linux daemon. A native scripting target on macOS or Linux. Anywhere a multi-megabyte CPython install or a 30 MB Pyodide download is too much, and yet you still want real Python (floats, dicts, classes, comprehensions, dataclasses, the everyday stdlib), nanopython is a small runtime that fits.

Why nanopython exists

There are three good ways to run Python today. Each has a place it doesn't belong.

  • CPython is where Python is real. It's also multi-megabyte at rest, hundreds of milliseconds cold, and comes with a stdlib that assumes a full operating system.
  • Pyodide brings CPython to the browser at roughly 30 MB of wasm plus a stdlib download. Excellent for scientific-computing kernels; heavy for a page that just wants a Python snippet.
  • MicroPython proved a small Python is possible. Its target is 256 KB flash on a microcontroller, and its distribution ecosystem, its stdlib coverage, and its wasm story all reflect that focus.

Between those, there's a gap: the surfaces where you want real Python, but where "multi-megabyte" is too much and "microcontroller" is the wrong shape. A browser tab. A stdlib-only Linux daemon. A native binary you want to ship at under a megabyte. nanopython is aimed at that gap.

What you get

Three concrete outcomes today (all measured, all reproducible):

  • In a browser tab. ~270 KB of wasm after wasm-opt -Oz, loads in well under a second on a typical connection. Python code touches the DOM directly through import js (ADR 006): js.document.body.textContent = "hi" works from Python.
  • As a small Linux daemon. hop3-rootd is a 6 KLOC stdlib-only privileged Linux daemon (systemd-managed, Unix-socket RPC, real cgroup / mount / firewall orchestration). Running on nanopython it uses ~3.3 MB RSS vs ~17.4 MB on CPython 3.14: a 5.3× reduction. Full case study in TR-002.
  • As a native scripting target. ~550 KB static binary on arm64/x86_64 macOS or Linux. Ships the POSIX subset a Python daemon reaches for: os, socket, select, signal, subprocess, pwd, grp, plus pathlib, datetime, dataclasses, logging, unittest.mock, pytest_lite.

Who this is for

  • Web developers who want Python running in a browser tab without a 30 MB toolkit and a WASI shim they didn't choose.
  • Systems developers with a stdlib-only Linux daemon or a small scripting target, who care about RAM and startup time.
  • Anyone curious about a real-language runtime built as an incremental C→Zig strangler under a byte-exact oracle. The methodology is documented as a self-contained paper in TR-001.

What's here now

Native Wasm
Byte-exact against vendor MicroPython (nano config) 541/541 490/541 (52 documented walls)
Extended-semantics corpus (full config) 545/552 (98.7%) n/a
Integration smoke (both arms) 77/77 77/77
Cold-start RSS on a real 6 KLOC Linux daemon ~3.3 MB vs CPython's ~17.4 MB n/a
Shipping artifact size ~550 KB ReleaseSmall ~270 KB post-wasm-opt -Oz

v0.7.1 is the current release; v0.8 is in flight, headline is the full-unix profile that made the hop3-rootd result possible.

Where to go next


How it works (in a paragraph)

nanopython started as a strangler port of MicroPython to Zig, verified byte-for-byte against MicroPython's own test corpus at every commit. Zig's error union (PyError!T) replaced MicroPython's setjmp/longjmp-based exception machinery, which is what made wasm viable (wasm lacks native setjmp). Per-feature build flags decompose the runtime into orthogonal lanes, so each build is exactly the surface a given target needs. The single explicit compatibility contract today is the .mpy v6 bytecode format: zigpy-compile's output runs on the upstream MicroPython VM, and vice-versa. Beyond that, nanopython has been diverging as its own use cases pulled it in useful directions; see vs MicroPython for the current divergences and Architecture for the runtime's shape.