Skip to main content

PPY — Pretty Python

PyPI Python CI Docs License

Python's syntax, compiled. A .ppy file is valid Python and runs under plain CPython with no compiler involved. The compiler adds a strict static checker, an optimized Python backend, and an LLVM native backend — and the three must print the same answer, or it is a bug.

uv add "ppy-lang[llvm]"        # or: pip install "ppy-lang[llvm]"

Pin an exact version before 1.0 (ppy-lang[llvm]==0.2.0): a minor release may move the language, and the changelog says what moved. Extras enable the rest — solver, numpy, torch, jax, pydantic, uvicorn, scipy, pandas, pyarrow, bind — and a missing library only disables its plugin. uv run ppy doctor reports what it found; the details are in installing.

One file, four ways

# collatz.ppy
import ppy


@ppy.pure
@ppy.opt(3)
def longest(limit: int) -> int:
    best: int = 0
    for start in range(1, limit):
        n: int = start
        steps: int = 0
        while n != 1:
            n = n // 2 if n % 2 == 0 else 3 * n + 1
            steps += 1
        best = max(best, steps)
    return best


print(longest(ppy.input[int]()))

echo 300000 | before each of these:

uv run python    collatz.ppy           # 1  plain CPython, no compiler   1068.8 ±  5.5 ms
uv run ppy       collatz.ppy           # 2  optimized Python backend     1074.3 ± 10.3 ms
uv run ppy run   collatz.ppy           # 3  LLVM, built on the first run   41.7 ±  1.8 ms  (+ ~640 ms, once)
uv run ppy build collatz.ppy -o dist   # 4  LLVM, built once (~640 ms)...
./dist/collatz                         #    ...then the native binary      30.8 ±  0.4 ms
                                       #    ...--host-cpu, not portable    28.8 ±  0.5 ms

gcc   -O3 collatz.c && ./a.out         # the same loop in C                41.9 ±  0.9 ms
clang -O3 collatz.c && ./a.out         #                                   32.8 ±  0.5 ms

Kernel wall time on one machine, ten fresh processes each, mean ± standard deviation. Ways 3 and 4 are one compiler: the binary is ppy run in a compiled coat, and the first ppy run is the build into the cache. They differ in one default. ppy run keeps Python's integers — overflow is guarded and falls back to arbitrary precision — and lands on gcc; ppy build is a wrap-semantics artifact like every native compiler and lands on clang. run --unsafe and build --safe flip either; bounds checks stay in both.

The same kernel through the neighbours, same machine and methodology:

compiler kernel integer semantics
PPY ppy build --host-cpu 28.8 ± 0.5 ms 64-bit, wraps on overflow (this machine's instruction set)
C (clang -O3 -march=native) 30.8 ± 0.7 ms 64-bit, wraps on overflow
PPY ppy build 30.8 ± 0.4 ms 64-bit, wraps on overflow (--safe to keep Python ints)
Numba @njit 32.5 ± 0.9 ms 64-bit, wraps on overflow
C (clang -O3) 32.8 ± 0.5 ms 64-bit, wraps on overflow
PPY ppy run 41.7 ± 1.8 ms Python ints: guarded, falls back to arbitrary precision
C (gcc -O3) 41.9 ± 0.9 ms 64-bit, wraps on overflow
Codon -release 42.1 ± 12.1 ms 64-bit, wraps on overflow
PyPy 3.11 52.8 ± 0.5 ms Python ints
Cython (cdef long long) 61.6 ± 0.8 ms 64-bit, wraps on overflow
mypyc 71.6 ± 1.4 ms Python ints
Nuitka 714.7 ± 12.8 ms Python ints, no type specialization
CPython 3.14 1068.8 ± 5.5 ms Python ints

--host-cpu compiles for the machine doing the build instead of the portable baseline; it is off by default because an artifact is meant to be shipped. JIT code under ppy run always targets the host, which is free because it never leaves the machine. gcc -O3 -march=native changes nothing here (42.1 ± 1.3 ms), and Codon's spread is its own: ten runs between 35 and 60 ms. Ports are the straightforward one for each tool: @njit, a cdef long long .pyx, an annotated module for mypyc, codon build -release, nuitka --module; Numba 0.67 (warm), Cython 3.3, mypy 2.3.1, Nuitka 4.2 on CPython 3.13, Codon 0.19.6, PyPy 3.11.15 (warm), gcc 13.3, clang 22.1, CPython 3.14.5. Eight more kernels and six whole programs against both C compilers are in examples/15_algorithms.

Turning ordinary Python into it

uv run ppy convert src/ --in-place    # strict: the output must be valid strict PPY
uv run ppy migrate src/ --in-place    # permissive: rewrite toward it, report the rest

convert is the compiler-facing command: it refuses to produce anything ppy check would reject, and says why. migrate is for normal existing Python — it first rewrites dynamic-but-static patterns (setattr with a constant name, globals()["X"] = ..., constant importlib.import_module), then staticizes, and classifies whatever remains (--report migration.json, --diff).

Given untyped Python:

import math

LIMIT = 3.0


def clamp(value):
    return min(value, LIMIT)


def spread(samples):
    total = 0.0
    for sample in samples:
        total += clamp(sample)
    return math.sqrt(total / len(samples))


print(spread([1.0, 2.0, 9.0]), spread((4.0, 5.0)))

ppy convert writes:

import math
from collections.abc import Sequence
from typing import Final

import ppy

LIMIT: Final[float] = 3.0


@ppy.pure
def clamp(value: float) -> float:
    return min(value, LIMIT)


@ppy.pure
def spread(samples: Sequence[float]) -> float:
    total: float = 0.0
    for sample in samples:
        total += clamp(sample)
    return math.sqrt(total / len(samples))


print(spread([1.0, 2.0, 9.0]), spread((4.0, 5.0)))

Types come from the whole call graph, not one file: samples is Sequence because the body only reads it and one call site passes a tuple, LIMIT is Final because nothing rebinds it, and @ppy.pure is attached only where the checker proved it. Nothing is renamed and no function is split.

By the numbers

3 execution paths that must agree — plain CPython, the Python backend, LLVM — checked on every example by examples/run_all.py 1,110 tests on Python 3.12, 3.13, and 3.14 covering 74% of the compiler's 36k statements, plus differential fuzzing of generated programs
42 example folders, 52 programs, every .ppy next to a .py regenerated by verify_conversions.py to prove it is what ppy convert wrote 76 diagnostic codes, each documented once, each with a stable meaning
18 IR dialects — core, math, simd, cpu, atomic, concurrency, parallel, tensor, linalg, fft, sparse, columnar, arrow, gpu, async, prof, … 7 backends off one IR: LLVM (JIT and objects, host or cross), C11, C++17, CUDA, HIP, NVVM/PTX, StableHLO
8 library plugins — NumPy, PyTorch, JAX/Flax, pydantic, FastAPI/Uvicorn, SciPy, pandas, PyArrow — each a faster path where equivalence is proven and the library's own call everywhere else 0 Python frames on a native call: 47 ns for a two-int call against 28 ns for a plain Python call, 65 ns with a borrowed buffer, 86 ns when a guard fails and the Python body runs

The whole native surface — ppy.native memory and FFI, ppy.simd, ppy.cpu, ppy.atomic, ppy.concurrent, ppy.parallel.range, ppy.grad, ppy.aio coroutines, ppy.cuda/ppy.hip kernels, ppy.xla.jit — has a Python reference implementation, so a program using it is still a Python program. What is not native is untouched: a PyTorch training loop is .backward() and the optimizer, and stays exactly as fast.

Docs

ppy.franknoh.dev — getting started, the guide, every example with what it prints, the CLI, the API, and the internals. Built from docs/ on every push; latest is the release, dev the tip.

Development

git clone https://github.com/franknoh/PPy.git
cd PPy
uv sync              # the compiler core, LLVM, NumPy, pydantic, and the linters
./scripts/check.sh   # the one gate; CI runs exactly this

Plugin runtimes are separate groups (uv sync --group torch|jax|uvicorn|all), and everything else a contributor needs — the gate, the invariants a change has to keep, how examples and measurements are kept current — is in CONTRIBUTING.md.

Release files for ppy-lang 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ppy-lang 0.2.0
File Size Uploaded
ppy_lang-0.2.0.tar.gz 909.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ppy-lang 0.2.0
File Interpreter ABI Platform
ppy_lang-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.6 MB

Release files / ppy_lang-0.2.0.tar.gz

Download URL ppy_lang-0.2.0.tar.gz
Size 909.6 kB
Tags Source
SHA-256 checksum
How to use checksums
c50fc21775ea100d0cc07e9b6ae666509932accec9335228791cc41c710487a2
BLAKE2b-256 checksum
How to use checksums
4b56619b322172ca9ecdd683c9d5ac77fb7a4657da5562bf89e94b339f8119de
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release files / ppy_lang-0.2.0-py3-none-any.whl

Download URL ppy_lang-0.2.0-py3-none-any.whl
Size 733.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cb885c60d29fc8f618073bfbbe863780ee80d8ac87a7ba6e2eb38314f3a51b36
BLAKE2b-256 checksum
How to use checksums
18723d0c8d56ec454bfe13fcaead19b0c2a477ffc00e9cd9a5839d5121c49f01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.7

2 release files

0.3.6

2 release files

0.3.5

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page