The other day I discovered an interesting library that I had never heard of before.
PythonC is a domain-specific language (DSL) compiler that allows developers to write C programs using standard Python syntax. It takes a statically typed subset of Python code and compiles it directly to native machine code via LLVM IR (Low-Level Virtual Machine Intermediate Representation).
LLVM IR is A platform-independent code format used internally by the LLVM compiler framework. The compiler first converts the source code into LLVM IR, and then LLVM converts the IR into machine code optimized for a specific CPU (x86, ARM, etc.).
The core design philosophy of PythoC is: Runtime equivalent to C + compile time using Python, And it has almost unique selling points such as:
1. Create a standalone native executable
Unlike tools such as Cython, which are primarily used to create C extensions to speed up existing Python scripts, PythoC can generate completely independent, standalone C-style executables. Once compiled, the resulting binary does not require a Python interpreter or garbage collector to run.
2. Python syntax allows low-level control
PythoC mirrors the features of C, but wraps them in Python’s cleaner syntax. To accomplish this, we use machine-native type hints instead of Python’s standard dynamic types.
- primitive: i32, i8, f64 etc.
- Memory structure: pointer (ptr[T]), array (array[T, N]), and structures (created by decorating standard Python classes).
- Manual memory management: Memory management is explicit, similar to C, since there is no garbage collector by default. However, it provides modern optional safety checks such as: linear type (make sure all allocations are explicitly deallocated to prevent leaks) Filter type (to force compile-time validation checks).
Python as a metaprogramming engine
One of PythoC’s most powerful features is its handling of compilation steps. Because the compile-time environment is just Python, you can use standard Python logic to generate, manipulate, and specialize PythoC code. in front Compiles to LLVM. This gives you very flexible compile-time code generation capabilities (similar to C++ templates, but driven by pure Python).
Sounds promising, but does the reality live up to the hype? Now, let’s see this library in action. Installation is easy, like most Python libraries, just pip install:
pip install pythoc
However, it is better to set up a proper development environment that allows you to silo different projects. This example uses the UV utility, but feel free to use whichever method is most comfortable for you. Enter the following command in your command line terminal:
C:\Users\thoma\projects> cd projects
C:\Users\thoma\projects> uv init pythoc_test
C:\Users\thoma\projects> cd pythoc_test
C:\Users\thoma\projects\pythoc_test> uv venv --python 3.12
C:\Users\thoma\projects\pythoc_test> .venv\Scripts\activate
(pythoc_test) C:\Users\thoma\projects\pythoc_test> uv pip install pythoc
simple example
To use PythoC, define functions with a specific machine type and mark them for PythoC compilation. Decorator. There are two main ways to run PythoC code. You can call the compiled library directly from Python as follows:
from pythoc import compile, i32
@compile
def add(x: i32, y: i32) -> i32:
return x + y
# Can compile to native code
@compile
def main() -> i32:
return add(10, 20)
# Call the compiled dynamic library from Python directly
result = main()
print(result)
Then run it like this:
(pythoc_test) C:\Users\thoma\projects\pythoc_test>python test1.py
30
Alternatively, you can create a standalone executable that can be run independently of Python. To do this, use code like this:
from pythoc import compile, i32
@compile
def add(x: i32, y: i32) -> i32:
print(x + y)
return x + y
# Can compile to native code
@compile
def main() -> i32:
return add(10, 20)
if __name__ == "__main__":
from pythoc import compile_to_executable
compile_to_executable()
We do it the same way.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>python test4.py
Successfully compiled to executable: build\test4.exe
Linked 1 object file(s)
This time, no output is displayed. Instead, PythoC creates a build directory under the current directory and creates an executable file there.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>dir build\test4*
Volume in drive C is Windows
Volume Serial Number is EEB4-E9CA
Directory of C:\Users\thoma\projects\pythoc_test\build
26/02/2026 14:32 297 test4.deps
26/02/2026 14:32 168,448 test4.exe
26/02/2026 14:32 633 test4.ll
26/02/2026 14:32 412 test4.o
26/02/2026 14:32 0 test4.o.lock
26/02/2026 14:32 1,105,920 test4.pdb
You can run the test4.exe file just like any other executable file.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>build\test4.exe
(pythoc_test) C:\Users\thoma\projects\pythoc_test>
But wait a minute. In the Python code, I explicitly asked for the addition result to be printed, but no output is displayed. what happened?
The answer is that the built-in Python print() function relies on the Python interpreter running in the background to determine how to display the object. PythoC strips all of this away and builds small, super-fast native executables, so print statements are stripped away.
To print to the screen in native binary, you must use standard C library functions. printf.
How to use printf in PythonC
C (and therefore PythoC) requires a format specifier to print variables. Write a string containing a placeholder (such as %d for a decimal integer) and pass the variable to be inserted into the placeholder.
Here’s how to update your code to import the C printf function and use it correctly.
from pythoc import compile, i32, ptr, i8, extern
# 1. Tell PythoC to link to the standard C printf function
@extern
def printf(fmt: ptr[i8], *args) -> i32:
pass
@compile
def add(x: i32, y: i32) -> i32:
printf("Adding 10 and 20 = %d\n", x+y)
return x + y
@compile
def main() -> i32:
result = add(10, 20)
# 2. Use printf with a C-style format string.
# %d is the placeholder for our integer (result).
# \n adds a new line at the end.
return 0
if __name__ == "__main__":
from pythoc import compile_to_executable
compile_to_executable()
Now, if you re-run the above code and run the resulting executable, the output will be as expected.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>python test5.py
Successfully compiled to executable: build\test5.exe
Linked 1 object file(s)
(pythoc_test) C:\Users\thoma\projects\pythoc_test>build\test5.exe
Adding 10 and 20 = 30
But is it really worth the trouble?
Everything we’ve talked about so far is only of value if you see real speed improvements in your code. So, as a final example, let’s take a look at how fast our compiled program is compared to its Python equivalent. This should clearly answer our question.
First, it’s regular Python code. Use recursive Fibonacci calculations to simulate long-running processes. Let’s calculate the 40th Fibonacci number.
import time
def fib(n):
# This calculates the sequence recursively
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
if __name__ == "__main__":
print("Starting Standard Python speed test...")
start_time = time.time()
# fib(38) usually takes around 10 seconds in Python,
# depending on your computer's CPU.
result = fib(40)
end_time = time.time()
print(f"Result: {result}")
print(f"Time taken: {end_time - start_time:.4f} seconds")
I got this result when I ran the above code.
(pythoc_test) C:\Users\thoma\projects\pythoc_test>python test6.py
Starting Standard Python speed test...
Result: 102334155
Time taken: 15.1611 seconds
Next, we’ll discuss the PythoC-based code. Again, as with the print statement in the previous example, you cannot just use regular import timing directives from Python for timing. Instead, you should borrow standard timing functions directly from the C programming language. clock(). This is defined in the same way as the printf statement used earlier.
This is an updated PythoC script with a built-in C timer.
from pythoc import compile, i32, ptr, i8, extern
# 1. Import C's printf
@extern
def printf(fmt: ptr[i8], *args) -> i32:
pass
# 2. Import C's clock function
@extern
def clock() -> i32:
pass
@compile
def fib(n: i32) -> i32:
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
@compile
def main() -> i32:
printf("Starting PythoC speed test...\n")
# Get the start time (this counts in "ticks")
start_time = clock()
# Run the heavy calculation
result = fib(40)
# Get the end time
end_time = clock()
# Calculate the difference.
# Note: On Windows, 1 clock tick = 1 millisecond.
elapsed_ms = end_time - start_time
printf("Result: %d\n", result)
printf("Time taken: %d milliseconds\n", elapsed_ms)
return 0
if __name__ == "__main__":
from pythoc import compile_to_executable
compile_to_executable()
My output this time is
(pythoc_test) C:\Users\thoma\projects\pythoc_test>python test7.py
Successfully compiled to executable: build\test7.exe
Linked 1 object file(s)
(pythoc_test) C:\Users\thoma\projects\pythoc_test>build\test7.exe
Starting PythoC speed test...
Result: 102334155
Time taken: 308 milliseconds
In this small example, the code is a bit more complex, but it shows the real benefits of using a compiled language like C. Our executable was 40 times faster than the equivalent Python code. Not too shabby.
Who is PythonC for?
There are three main types of PythoC users.
1/ As we saw in the Fibonacci speed test, standard Python can become slow when performing heavy mathematical lifting. PythoC can be useful for Python developers who build physics simulations, complex algorithms, or custom data processing pipelines and hit performance walls.
2/ Programmers who work closely with computer hardware (building game engines, writing drivers, programming small IoT devices, etc.) typically write in C because they need to manually manage the computer’s memory.
PythoC may be attractive to these developers because it offers similar manual memory control (using pointers and native types), but allows you to use Python as a “metaprogramming” engine to write cleaner and more flexible code before it is compiled to the hardware level.
3/ If you write a useful Python script and want to share it with a colleague, that colleague typically needs to install Python, set up a virtual environment, and download dependencies. This can be a hassle, especially if your target users have low IT literacy. However, with PythoC, once you create a compiled C executable file, anyone can run it by simply double-clicking the file.
And who is it not for?
The flip side of the above is that PythoC is probably not the best tool for web developers because the performance bottleneck is usually the network or database speed, not the CPU calculation speed.
Similarly, if you’re already using an optimized library such as NumPy, you won’t see much benefit.
summary
This article introduced a relatively new and unknown PythoC library. It allows you to write blazingly fast standalone C executable code using Python.
We have provided some examples of creating C executable programs using Python and the PythoC library. It also includes examples that show impressive speed improvements when running executables created by the PythoC library compared to standard Python programs.
One problem you might run into is that Python imports are not supported in PythoC programs, but we also showed you how to work around this problem by replacing them with equivalent C built-in functions.
Finally, I discussed my thoughts on which types of Python programmers might benefit from using PythonC in their workloads, and which types of Python programmers might not.
I hope this has inspired you to find out what kinds of use cases PythoC can be used for. You can learn more about this useful library by checking out the GitHub repository at the following link.
https://github.com/1flei/PythoC
