python-to-binary
py2bin turns Python into machine code using nothing but the Python standard
library. No Cython, Nuitka, mypyc, Rust, C, C++, PyInstaller, PPCI, bootloader,
assembler, linker or SDK - and no gcc or clang at any point. The only thing
a build needs is an interpreter.
pip install python-to-binary
py2bin compile-capi app.py --target darwin-arm64 -o app
Source, issues and the full documentation: https://github.com/yu314-coder/python_to_binary
Platforms
What compile-capi - the tier that turns your program into machine code that
drives CPython - can target today.
| x86-64 | arm64 | |
|---|---|---|
| macOS | ✅ works | ✅ works |
| Windows | ✅ works | ⬜ future work |
| Linux | ⬜ future work | ⬜ future work |
Each working target is held to the same standard: an 889-program corpus is compiled for it and every program's output and exit code compared against CPython's. macOS agrees on 878 and differs on 7; a 100-program slice run through Wine agrees on 93 and differs on 5. The differences are the same on every platform and are inherent rather than open - CPython's "Did you mean" needs a Python frame to suggest from, and the repr of a compiled function really is a builtin function's.
The native tier (py2bin compile, no CPython at all) targets all six.
The paths through it
compile-capi translates ordinary Python into C that drives the CPython
C API, then compiles that C with py2bin's own C compiler. This is the tier
Nuitka occupies, with Nuitka's dependency removed - Nuitka hands its generated
C to clang, and this compiles it itself. Your logic becomes machine code;
Python's object semantics stay in libpython, which the binary links.
compile removes CPython entirely: Python AST → py2bin IR → optimizer →
handwritten x86-64/ARM64 instructions → ELF, PE or Mach-O. Nothing is
interpreted and nothing is linked. The price is the smallest accepted subset.
freeze / bundle is compatibility packaging: your program shipped beside
an interpreter that runs it, which is how a project with NumPy or a GUI gets an
artifact at all. It is not translation.
Using it
pip install python-to-binary
| command | what it does |
|---|---|
py2bin compile-capi |
Python → C driving the CPython C API → machine code |
py2bin compile |
Python → machine code, no CPython anywhere |
py2bin compile-c |
py2bin's own C compiler, on your C |
py2bin freeze |
ship the program beside an interpreter |
py2bin targets |
list the targets this build knows |
Bundling a real application into a macOS .app that carries its own
interpreter and packages:
py2bin compile-capi app.py --target darwin-arm64 \
--app --name "My App" --icon icon.icns \
--embed-python --site ../Resources/site-packages \
--bundle-site /path/to/venv/lib/python3.14/site-packages \
--prune-unused --zip-stdlib \
-o dist/MyApp.app --clean
How it works
Nothing wraps a toolchain; each stage is a module you can read.
capi_emit.py Python AST -> C that calls the CPython C API
capi_ints.py which locals may live in a machine register
c_preprocessor.py #include, macros, conditionals
c_frontend.py C -> py2bin IR
native/ir.py the IR itself
native/optimizer.py constant folding, dead code, write merging
native/arm64.py IR -> ARM64 instructions
native/x86_64.py IR -> x86-64, System V and Microsoft x64
native/formats/ Mach-O, PE32+, ELF
freezer.py bundling: interpreter, packages, pruning, archives
cabi.py the vetted CPython entry points
So compile-capi is five stages, all of them in this package: capi_emit →
c_preprocessor → c_frontend → native.x86_64/native.arm64 →
native.formats.macho/pe.
There is no import ctypes anywhere on that path, which a test asserts by
compiling in a fresh interpreter and listing what got loaded. ctypes is
standard library and would pass an imports-only-stdlib check, but it pulls in
subprocess - and there are Pythons where a subprocess is not something a
program may have.
Measured against Nuitka
manim_app: 10,100 lines, pywebview + Pillow + pyobjc, built both ways on the same machine.
| py2bin | Nuitka | |
|---|---|---|
whole .app |
61.2 MB | 72.6 MB |
| main binary | 9.2 MB | 29.6 MB |
| bare interpreter start | 9.5 ms | 16.2 ms |
| start with the app's imports | 52.1 ms | 44.8 ms |
| compile time | 16.7 s | minutes |
Run time, median of 5, seconds:
| workload | py2bin | CPython | Nuitka |
|---|---|---|---|
| integer arithmetic | 0.050 | 0.084 | 0.095 |
while loop |
0.045 | 0.070 | 0.045 |
| nested loops | 0.022 | 0.035 | 0.042 |
| function calls | 0.065 | 0.023 | 0.020 |
| string building | 0.025 | 0.011 | 0.009 |
Loops beat both because a local the analysis picks out is held in a register rather than on the heap, with the overflow check that falls back to unbounded arithmetic when it leaves the word. Calls still lose: an argument is boxed at the call and unboxed inside, where the interpreter's specialised call pays neither.
Licence
MIT. Full documentation, source and issues: https://github.com/yu314-coder/python_to_binary
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_to_binary-0.3.0.tar.gz.
File metadata
- Download URL: python_to_binary-0.3.0.tar.gz
- Upload date:
- Size: 703.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
01805c6e226000ac97ea9f58cf159be8fada81aad34b53de109b8d6fa0b09bc7
|
|
| MD5 |
75a8e0d9c403bb3250de2d8f86d6d524
|
|
| BLAKE2b-256 |
59b879f28774ff64baf13e779f919563d19feb705a599d37f99beb81fafc95e4
|
File details
Details for the file python_to_binary-0.3.0-py3-none-any.whl.
File metadata
- Download URL: python_to_binary-0.3.0-py3-none-any.whl
- Upload date:
- Size: 470.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
70344a48c261d69108973865ab8dbf0f294ed5b623da8b6bb236d462881d995b
|
|
| MD5 |
3a59fe4b327917983a7f53306e1a5861
|
|
| BLAKE2b-256 |
7f75e8c52f1f02580c4b1e10fc4cfacdd9ea85e7d3b87f4b2554f61c7cc6d0a6
|