TurboPython: a Python-to-C++ compiler
TurboPython (TPy, package tpy-lang) compiles statically typed Python
through C++ to a native binary. The result has no interpreter, no garbage
collector, no automatic reference counting and no GIL; an ownership model
provides deterministic, compile-checked memory management. Source files
stay valid Python, so existing editors and linters read them, but
TurboPython is not a drop-in replacement for CPython.
Website | Documentation | Examples | Release notes
Status: early development. The core language compiles and runs real programs, but some ordinary Python constructs are still rejected, the standard library is a subset, and known bugs can produce wrong results silently. See what works before relying on it.
Example
The source is Python with type annotations and two TurboPython types,
int32 and Own:
from dataclasses import dataclass
from tpy import Own, int32
@dataclass
class Stats:
count: int32
total: int32
longest: int32
def measure(words: list[str]) -> Own[Stats]:
total, longest = 0, 0
for w in words:
total += len(w)
longest = max(longest, len(w))
return Stats(len(words), total, longest)
s = measure("the quick brown fox jumps over the lazy dog".split())
print(s.count, s.total, s.longest)
tpy compiles and runs it; tpyc -b leaves a standalone binary:
$ tpy stats.py
9 35 5
$ tpyc -b stats.py
$ ./__tpyc__/stats.d/release/stats
9 35 5
int32 is a fixed-width integer. Integer literals and len() are int32
by default, so total and longest need no annotation, and arithmetic on
them is checked: an overflow stops the program with an error instead of
wrapping. Plain int stays arbitrary-precision, as in Python. Stats
becomes a plain struct of three 32-bit integers and the loop runs over the
list's storage directly: nothing is heap-allocated per object,
reference-counted or garbage-collected. Own[Stats] says the function
hands its result to the caller; see
Ownership.
Installation
Requirements: Python 3.12+ and a C++23 compiler on the PATH (g++ 13+ or clang++ 19+). Linux and macOS are supported; on Windows, WSL is the recommended setup.
$ pip install tpy-lang
$ pip install "tpy-lang[bundled]" # also installs zig as a bundled C++ compiler
Or as an isolated tool: uv tool install tpy-lang or pipx install tpy-lang.
Two commands are installed: tpy runs programs and drops to a REPL with no
arguments; tpyc compiles only and emits .hpp/.cpp files or a binary.
Usage
$ tpy # interactive REPL
$ tpy -c "print(1 + 2)" # run one line
$ tpy --debug app.py # unoptimized build with debug info (-g -O0)
$ tpy --dump-code app.py # print the generated C++
$ tpyc -o out/ app.py # emit .hpp/.cpp into out/
Building & running covers every command and flag, and the getting-started guide walks through a first program.
How it differs from Python
- Function parameters and return types are annotated (a missing return
annotation means
-> None), and class fields are declared with annotations; local variables are inferred. - Integer literals and
len()areint32, with checked overflow;intstays arbitrary-precision. - Every value has exactly one owner: a function frame, a field or a
container. Storing an object that stays in use afterwards copies it, and
the compiler warns where CPython would have shared a reference.
Own[T]moves a value instead. - No GIL:
spawnruns real OS threads and checksSend/Syncat compile time. A task that is notSend, or state shared throughArcthat is notSync, is rejected; shared mutable state goes throughArc[Mutex[T]],RwLock, atomics or channels. - No CPython packages: everything a program imports is TurboPython source,
compiled with it. Dependencies are TurboPython packages, either the
standard-library subset and
tplibthat ship with the compiler, or others written for it. - Running a file under CPython, or type-checking its
tpyimports, needs thetpystub package from the source tree (lib/cpy) on the path; it is not yet part of the installed package.
The ownership rule is the one deep difference. A list owns its elements, so appending an object that is still in use afterwards stores a copy, and the compiler says so:
from dataclasses import dataclass
@dataclass
class Reading:
sensor: str
def main() -> None:
log: list[Reading] = []
r = Reading("boiler-3")
log.append(r) # r is used below, so the list gets a copy
r.sensor = "renamed"
print(log[0].sensor) # boiler-3 -- not renamed
main()
warning: copies Reading into owned storage; use copy() to make this explicit
Under CPython this program prints renamed. The program still compiles, and
the warning makes the difference visible at compile time; writing copy(r)
(from tpy import copy) states the intent and silences it. Unlike Cython, mypyc or Nuitka, the
output does not use the CPython runtime; unlike Codon and Shed Skin, memory
is managed by ownership instead of a garbage collector.
How TPy differs lists every
difference with the compiler's actual diagnostics, and
Ownership teaches the model.
Status
TurboPython is pre-1.0 and changes between releases; read the release notes before upgrading.
- What works is listed per feature and per standard-library module in Compatibility.
- What is broken is tracked in the open, in
BUGS.md. It is long. Most entries are valid Python the compiler still rejects with an error; the ones that matter most are the silent miscompiles, and those are fixed first. - Memory safety is partial. The compiler rejects a reference that would
outlive its owner, but it does not enforce Rust's aliasing rules: mutating
a container while a reference into it is live draws a warning, not an
error. Known holes in the escape check are tracked in the Safety section
of
BUGS.md. - How it is checked: more than 6,500 test programs. Each pins the compiler's diagnostics and the generated C++; the ones that run are built, executed, and -- wherever the program is also valid CPython -- byte-compared against CPython's output.
- Bug reports are welcome, especially a program that compiles and prints something CPython does not: open an issue.
Design goals
- Efficient native code -- no interpreter, garbage collector or
automatic reference counting, and opt-in constraints such as
@noallocfor hot paths. Where efficiency and CPython compatibility conflict, efficiency wins. - Explicit divergence -- a construct that behaves differently from CPython is rejected or warned about, with a diagnostic that names the fix.
- Valid Python source -- existing editors, linters and coding agents read TurboPython code without plugins.
- C/C++ interop -- generated code links with existing C and C++ code,
and calls go in both directions: TurboPython calls native functions and
types through
@native, and native code calls exported TurboPython functions. - Checked concurrency -- no GIL; what crosses a thread boundary is checked at compile time.
Using the generated C++
tpy --dump-code prints the C++ the compiler writes, and tpyc emits it
as .hpp/.cpp files for use in a C++ project. The Stats class and the
measure function from the example above come out as:
namespace tpyapp::stats {
struct Stats {
int32_t count;
int32_t total;
int32_t longest;
Stats() = default;
explicit Stats(int32_t count, int32_t total, int32_t longest);
// ... __eq__, __repr__, operator== and a class-name constant
};
Stats measure(const std::vector<std::string>& words) {
int32_t total = 0;
int32_t longest = 0;
auto& __obj_0 = words;
auto __beg_0 = __obj_0.begin();
auto __end_0 = __obj_0.end();
for (; __beg_0 != __end_0; ++__beg_0) {
std::string_view w = *__beg_0;
total = ::tpy::add_check<int32_t>(total, ::tpy::__len__(w));
longest = ::std::max(longest, ::tpy::__len__(w));
}
return Stats(::tpy::__len__(words), total, longest);
}
} // namespace tpyapp::stats
add_check is the overflow-checked add; words is borrowed as a const&
and each w is a std::string_view into it, with no copies.
With an explicit output directory (tpyc -o out/ myapp.py), a
sources.cmake and the runtime headers and sources are written next to the
generated code, so out/ is self-contained and can be committed or copied
to another machine:
include(path/to/out/sources.cmake)
add_executable(myapp ${TPYC_SOURCES})
target_include_directories(myapp PRIVATE ${TPYC_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE ${TPYC_LIBRARIES})
set_target_properties(myapp PROPERTIES CXX_STANDARD ${TPYC_CXX_STANDARD})
The generated code needs C++23 and the GCC statement-expression extension:
g++ 13+, clang++ 19+ or another LLVM-based compiler. MSVC is not supported.
Existing C and C++ code is reached through @native declarations, a
TurboPython function gets C linkage with @export(binding="C"), and a
module marked # tpy: ext_module builds into a regular CPython extension;
see Emit and integrate C++
and Native interop.
Coding with an AI agent
TurboPython source is valid Python, so coding agents work without a plugin.
What they lack is where TurboPython departs from Python.
tpy --install-agent-docs docs/ writes four reference files into a project
(a Python-to-TPy bootstrap, the language reference, standard-library
coverage and the exact API of the installed version) and prints a snippet
to add to AGENTS.md or CLAUDE.md. Re-running it after an upgrade
refreshes them.
Details.
Development
From a source checkout: uv sync, then uv run tpy app.py and
uv run pytest. docs/ARCHITECTURE.md describes the compiler.
License
Apache License 2.0 with the LLVM Exceptions -- see
LICENSE. The
exception covers the runtime and library code that is compiled into every
program: a binary built with tpy can be distributed without carrying the
license text or a notice. The bundled third-party libraries keep their own
licenses, listed in
NOTICE.
Metadata
Release files for tpy-lang 0.6.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| tpy_lang-0.6.2-py3-none-any.whl | Python 3 | none | any | Details for tpy_lang-0.6.2-py3-none-any.whl |
Release files / tpy_lang-0.6.2-py3-none-any.whl
| Download URL | tpy_lang-0.6.2-py3-none-any.whl |
|---|---|
| Size | 7.9 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f7161bde3d7e425ca486c1d6146dbd5ec5bb073e2025c89437f9b88389bc774f
|
|
BLAKE2b-256 checksum How to use checksums |
8beadd16aced466cdfb751e16f51226b6f050bd4ddc23945134d28342eaf0300
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.21 {"installer":{"name":"uv","version":"0.9.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for tpy-lang 0.6.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.