pocketpy: a C11 Python 3 interpreter you link into a game
Portable Python 3.x Interpreter in Modern C for Game Scripting
At a glance
- What is it?
- pocketpy is a portable Python 3.x interpreter written in C11, aimed at game scripting rather than at replacing CPython on a build machine. It links as a CMake target or a single header pair, exposes a C API and pybind11 compatible bindings, and is candid about both the speed claim and the build settings you cannot skip.
- Who is it for?
- pocketpy earns its place when the scripting layer has to ship inside a game client, a mobile app or a 32-bit microcontroller, where CPython is not an option and one C11 source file beats a runtime. It is the wrong choice for tooling, data work, or any script that assumes the standard library at full breadth.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 15, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A C11 interpreter built to be linked, not installed
pocketpy is a Python 3.x interpreter written in C11 whose delivery mechanism is the whole point: one source file and one header that you compile into your own project. There is nothing to install and no interpreter binary to ship alongside your game. You call `py_initialize` at startup, run a script with `py_exec`, and call `py_finalize()` when you shut down.
The audience is game developers, and the README positions the project as an alternative to Lua rather than as a substitute for CPython on a build machine. That framing drives the design. A game client has a fixed platform matrix, a hard memory ceiling and an update cycle measured in months, so the constraint the README emphasises is footprint, not fidelity to the Python standard library. Script authors get Python-shaped data instead: lists, dicts, classes, decorators, f-strings, match statements.
There is a second path for host projects that already speak C++. The README says bindings can be written through the C-API or through pybind11 compatible interfaces. The no-dependency claim covers the runtime; pybind11 sits on the host side of that build, so this route is not the single-file one.
The code ships under MIT, and the badge at the top of the README links to `github.com/blueloveth/pocketpy` rather than to the `pocketpy/pocketpy` path the project lives at now, so the LICENSE file in the tree is the text to read.
The supported platform list runs from desktop to microcontroller
The list names Windows 64-bit, Linux at both 64-bit and 32-bit, macOS 64-bit, Android at both widths, iOS 64-bit, Emscripten 32-bit, Raspberry Pi OS 64-bit, and the Luckfox Pico SDK at 32-bit. Two entries are unusual for a scripting language, and together they describe the target. Emscripten at 32-bit is the browser path through WebAssembly, and a 32-bit microcontroller SDK puts the interpreter on hardware where a full CPython build would never fit.
The compiler requirement is stated as a rule rather than a list: any platform with a C11 compiler is claimed to work, and the repository's own topic tags include c11 and header-only. On Windows the claim narrows sharply. Only MSVC is officially supported, so a MinGW or GCC toolchain sits outside what the project tests.
The tree shows how much of that coverage is carried by hand. `build_ios_libs.sh`, `build_darwin_libs.sh`, `build_android_libs.sh`, `build_web.sh`, `build_dll.sh` and `build_g_32.sh` sit at the repository root next to `build.sh`, which is what a header-only library looks like once it has to serve phones, browsers and desktops from one source tree.
Adding pocketpy through CMake or one file pair
There are two documented routes, and they differ in what you own afterwards.
The CMake route clones the repository as a submodule and adds two lines to your build file:
add_subdirectory(pocketpy)
target_link_libraries(<your_target> pocketpy)That is the entire integration step. The README points at `CMakeLists.txt` in the repository for the details, then spells the same file as `CMakelists.txt` in the instruction itself, which is the kind of typo that costs a search. The same section says it is safe to use the `main` branch in production if the CI badge is green. That is an unusual instruction to hand a studio: a submodule build is pinned to a branch that keeps moving, and the condition attached to it is a badge image rather than a version.
The second route skips the build system. You take the amalgamated pair, `pocketpy.h` and `pocketpy.c`, include the header, and there is no target to link. If you would rather generate that pair from a checkout than fetch it, the command is:
python amalgamate.pyBoth files land in an `amalgamated/` directory. Note what that command means for your CI: a project whose runtime has no dependencies still expects a Python host for its own build tooling, and the tree carries `cmake_build.py`, `compileall.py` and `prebuild.py` alongside it.
NDEBUG is not an optional define in this build
Three compiler settings are mandatory. `--std=c11` has to be set. On MSVC, `/utf-8` and `/experimental:c11atomics` have to be set as well, which says plainly that the atomics path is not portable across toolchains yet. And `NDEBUG` should be defined for a production build, with the reason given in one line: without it you get poor performance.
That third setting is the one people miss. A client built in a debug configuration produces a slow interpreter, and the cost does not announce itself. It surfaces in a profile, where it reads as a slow script layer rather than as a missing define, and by then the assumption about which side is at fault has usually hardened.
The repository root carries `compile_flags.txt` and a family of build scripts, among them `build_g_threads.sh`, which is the only sign that threading exists as a build option at all. The feature table has no row for threads, so if your script layer needs them, that question belongs to the build configuration rather than to the documented API.
The C API is a value stack driven by hand
Everything the host does to a Python object goes through a handle, and the README's own example walks the whole sequence: build a list in a temporary slot, call a builtin on it, then register a native function and call that from script.
py_Ref r0 = py_tmpr0();
py_newlistn(r0, 3);
py_newint(py_list_getitem(r0, 0), 1);
py_newint(py_list_getitem(r0, 1), 2);
py_newint(py_list_getitem(r0, 2), 3);
// Eval the sum of the list
py_Ref f_sum = py_getbuiltin(py_name("sum"));
py_push(f_sum);
py_pushnil();
py_push(r0);
ok = py_vectorcall(1, 0);The call shape is visible in that push sequence: the callable goes on first, nil goes where a receiver would go, the argument follows, and `py_vectorcall(1, 0)` fires it. The README does not document what the two numbers mean, so a host author reading only this page is inferring the calling convention rather than reading it.
Native functions take `py_Ref argv` and validate it themselves:
PY_CHECK_ARGC(2);
PY_CHECK_ARG_TYPE(0, tp_int);Those macros are the only guard a host function has, so skipping them means trusting every script that can reach the binding, which matters in a client whose script layer players can extend. Registration takes two calls, `py_newnativefunc` and `py_setglobal`, after which Python code calls `add(3, 7)` and the host reads the answer with `py_toint(py_retval())`. Errors are booleans rather than jumps: every `py_exec` in the example returns a bool, and the failure path calls `py_printexc()` before finalizing.
A feature table that only lists what works
The feature table is the most useful page in the repository and the least complete one. It gives a row and a short example for if/else/elif, for and while loops, functions with default and star arguments, subclasses, lists, list comprehensions, slices, tuples, dicts, f-strings, unpacking, star unpacking, exceptions, reflection, imports, context blocks, type annotations, generators, decorators and match statements. Every row is marked as supported.
Read it as an inventory of successes rather than as a specification, because no row records what is missing. The table says nothing about which modules `import` can reach, and nothing about the standard library a Python author expects to find on the other side. For a game script that formats text, sorts a list and loads a save file, that silence decides whether the project is usable, and the README does not answer it.
One row deserves a second look in a shipping client. Dynamic Code is supported, covering `eval()` and `exec()`. Put that beside reflection and native bindings and a script layer with eval can reach the entire interpreter. The README documents no restricted execution mode, no module whitelist and no sandboxing story, so if your players can edit scripts, that design question is yours to answer.
One sentence of speed evidence and no numbers
The Performance section makes one claim: pkpy is as fast as cpython 3.9. There is no table behind it, no test machine, no workload, and the comparison is pinned to a single CPython version rather than to CPython in general.
The harness is in the repository even though the evidence is not in the documentation. A `benchmarks/` directory sits at the root next to `run_tests.sh`, and neither appears in the integration instructions, so there is no documented command to run them and no published baseline to compare against. For an engine team this is the first thing to check on your own workload, because the entire argument for replacing a mature scripting layer rests on that single sentence.
The alternative the project names is Lua, and the argument is syntax rather than runtime model. Lua gives you tables, a small C API and years of shipped games; pocketpy gives your Python-trained staff the syntax they already write, with classes, decorators, generators and match statements in the same script. The other direction is CPython itself, with identical syntax and an entire standard library, at a footprint that fits neither a 32-bit microcontroller target nor a mobile app's memory budget. A team that already ships Lua keeps its API and its debuggers; a team that writes Python gets the syntax and loses the library.
The last push landed on 2026-09-28, after the v2.2.0 tag dated 2026-09-04, so the `main` branch the CMake route points at runs ahead of any version you can pin.
Badges pointing at a different repository path
One corner of the README has not moved with the project. Two badges at the top resolve to `github.com/blueloveth/pocketpy`: one linking to the legal text, one showing the newest downloadable build. The coverage badge uses `codecov.io/gh/pocketpy/pocketpy`, and so do the instructions for fetching the amalgamated files. Two repository names on one page, with no note about the difference.
This is a link-layer problem rather than a code problem. The legal text sits in the tree at the root, and the coverage badge agrees with the current path. But an automated check that follows badges instead of files resolves the old organisation, and a reader who starts at the top of the page is told which project this belongs to by a link that goes elsewhere.
Two documentation homes exist as well. The homepage field points at `https://pocketpy.dev/`, while the README sends readers to `https://pocketpy.github.io` for details, the live Python demo and the live C examples. Those demos are the fastest way to judge the API without a compiler, and they sit on the second domain rather than the first.
Editorial conclusion
pocketpy earns its place when the scripting layer has to ship inside a game client, a mobile app or a 32-bit microcontroller, where CPython is not an option and one C11 source file beats a runtime. It is the wrong choice for tooling, data work, or any script that assumes the standard library at full breadth. Before committing, run run_tests.sh from a checkout and read the single sentence in the Performance section yourself, because the speed claim arrives with no numbers and no baseline to compare against.
Frequently asked questions
How do I add pocketpy to an existing C++ game project?
Clone the repository as a submodule and add `add_subdirectory(pocketpy)` plus a `target_link_libraries` line to your CMake file, or take the amalgamated `pocketpy.h` and `pocketpy.c` pair and include the header with no target to link. Either way `--std=c11` must be set.
Does pocketpy need a Python installation to run?
No. The README states there are no dependencies other than the C standard library, and what you get is C11 source compiled into your own project. The build tooling in the repository, including `amalgamate.py` and `cmake_build.py`, does expect a Python host on the machine doing the build.
Which Python syntax does pocketpy support?
The feature table covers if/else/elif, for and while loops, functions with default and star arguments, subclasses, lists, list comprehensions, slices, tuples, dicts, f-strings, unpacking, exceptions, reflection, imports, context blocks, type annotations, generators, decorators and match statements. It does not say which modules an import statement can reach.
How does pocketpy compare with CPython and with Lua?
The README states that pkpy is as fast as cpython 3.9 and attaches no numbers, test machine or workload to that claim. Against Lua the trade is Python syntax for a mature C API and a long record of shipped games; against CPython it is a much smaller footprint for a much smaller standard library.
Can I build pocketpy on Windows with GCC or MinGW?
Only MSVC is officially supported on Windows. That toolchain also needs `/utf-8` and `/experimental:c11atomics`, and `NDEBUG` has to be defined for a release build, because a build without it runs the interpreter poorly.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pocketpy-pocketpy)Was this useful?