Compile Python to Native Code Without Learning Cython Syntax
Here’s the question if you ship proprietary Python software: can you turn your plain .py files into native machine code without learning Cython syntax, maintaining .pyx files, or hand-tuning a build pipeline?
You can. Py2Native exists precisely for that workflow. You write Python the way you already do, run one command through uv, and get a native executable or importable shared library. This article explains what that means, why the traditional route is more painful than it needs to be, and how to compile a Python project without becoming a Cython expert.
What Does “Compile Python to Native Code” Actually Mean?
In normal Python execution, your source code is compiled to bytecode, and the CPython interpreter reads that bytecode at runtime. The CPU never sees your Python logic directly. The interpreter is always in the middle.
Native compilation is different. It translates your Python logic into C, then compiles that C into machine code that the CPU executes directly. The result is still a Python extension module or an executable, but the part that represents your code is no longer readable Python source or simple .pyc bytecode.
A useful analogy is translation. Bytecode is like giving a chef a shorthand recipe in a language only the head chef understands—the chef still needs to be present every time. Native compilation is like translating the recipe into the kitchen equipment’s own physical instructions, so the machines can run it without the chef reading every step aloud.
Freezing tools such as PyInstaller bundle the interpreter and bytecode together. That hides your source from casual inspection, but the code can still be recovered as bytecode. True native compilation goes further because your modules become compiled machine code.
The Traditional Way: Cython, .pyx Files, and Build Headaches
Cython is powerful. It can speed up CPython code and is the engine underneath more than a few native wrapper projects. But using Cython directly usually means:
- Writing separate
.pyxfiles instead of or alongside your.pyfiles. - Learning Cython’s extended syntax and optional type annotations.
- Running
cythonizeto generate C code. - Invoking a platform C compiler and linker yourself.
- Maintaining build configuration for Windows, Linux, and macOS.
That is a lot of mental overhead if your primary goal is not squeezing out maximum performance, but simply protecting proprietary Python source code.
Py2Native takes a different approach. It still uses Cython under the hood, because Cython is excellent at turning Python into C. But Py2Native hides every Cython-specific step behind a managed build command. You do not write .pyx files. You do not run cythonize. You do not manage C compiler flags by hand.
How Py2Native Compiles Plain Python to Native Code
The zero-config entry point is a single command:
uv run py2native build main.py *.py
That expands your source globs, generates the Cython bootstrap internally, compiles the generated C, and links the result. The output is a native executable by default.
Under the hood, Py2Native:
- Globs your Python sources under the working directory.
- Generates a Cython bootstrap from its internal templates.
- Invokes Cython to turn the bootstrap into C.
- Compiles the C files with your platform compiler.
- Links the objects into an executable or shared library.
You never see the intermediate .pyx or .c files unless you go looking for them.
Py2Native supports two main output modes:
- Executable mode—the default
buildbehavior produces a native executable. - Library mode—
--libraryproduces a shared library such as.pydon Windows,.soon Linux, or.dylibon macOS, plus a Python package that imports from it.
Library mode is useful when you want your compiled code to remain importable from Python. The generated package includes an __init__.py that redirects imports to the native library and a __main__.py that enables python -m yourpackage.
You can also ask for useful deployment artifacts:
# Create a wheel containing compiled modules
uv run py2native build --wheel ./dist mypackage/*.py
# Create a self-contained deployment directory managed by uv
uv run py2native build --embed ./deploy main.py
The important detail for real-world projects is what happens to third-party libraries. They are not compiled. They remain as normal Python source and are imported normally. Py2Native compiles your custom modules, while dependencies such as Flask, NumPy, or SQLAlchemy keep working as-is.
Why Native Compilation Matters for Python Developers
The primary reason to compile Python to native code in this context is source protection. Raw .py files are easy to read. Bytecode in .pyc files can be decompiled with freely available tools. Native machine code raises the difficulty substantially.
That does not mean compiled code is impossible to reverse engineer. A determined analyst can still examine a native binary. But for most commercial software, the practical goal is to make casual copying and trivial source recovery difficult enough that honest customers stay honest and competitors cannot simply lift your implementation.
There are side benefits too:
- Distribution becomes simpler. You can ship a single native executable or a wheel without including your source files.
- CPU-bound sections can run faster. Native code removes some interpreter overhead, though this is not the primary selling point for a code-protection workflow.
- You keep the Python ecosystem. Because third-party libraries remain Python source, you do not have to rewrite your stack or configure builds for every dependency.
A common misconception is that native compilation requires giving up Python. Py2Native is not a Python-to-C translator that you must understand. It is a managed compiler. You stay in Python; the build system handles the native side.
Step-by-Step: Compile Your Python Project with Py2Native
Prerequisites
You need:
- CPython 3.11 through 3.15, including free-threaded 3.14t and 3.15t.
- A platform C compiler and linker: MSVC on Windows, GCC on Linux, or Clang on macOS.
uvto run the build command.- Internet access the first time, because Py2Native downloads required Python distributions and libraries.
The examples below assume py2native is declared as a project dependency so that uv run py2native resolves it automatically.
1. Compile an executable
From your project root, run:
uv run py2native build main.py *.py
Py2Native expands the glob to include your matching source files. If your entry point is main.py, the result is a native executable you can ship without the original .py files.
2. Compile a library
If you want an importable compiled package, use library mode:
uv run py2native build --library mypackage/*.py
This creates a shared library and the package files that bridge Python’s import system to it. You can also produce a wheel directly:
uv run py2native build --library --wheel ./dist mypackage/*.py
The wheel contains the compiled shared library plus the required __init__.py and __main__.py files.
3. Create an embedded deployment directory
To get a self-contained directory with its own uv-managed environment, use --embed:
uv run py2native build --embed ./deploy main.py
This is useful when you want a predictable runtime environment alongside the compiled binary.
4. Add Pro license verification
The open-source core is MIT-licensed. The closed-source Pro plugin adds JWT license verification. With the Pro plugin, the workflow is:
# Generate an EC P-256 keypair
uv run py2native keygen private.pem public.pem
# Sign a JWT payload with your private key
uv run py2native sign --private private.pem '{"sub":"customer-123","exp":2030-01-01T00:00:00Z}' license.dat
# Build with license verification enabled
uv run py2native build --license license.dat --public public.pem main.py *.py
Only the public key is stored in the executable. The Pro plugin bakes the signature verification code and public key into the native binary, so the license check is not a standalone Python file that someone can easily edit out.
To call the verifier from your code, you include the .pxd declaration file provided by the Pro plugin and call the verification logic with your license key. That is not “learning Cython”—it is using the small bridge the plugin provides to inject the compiled verifier into your executable. The result is that license enforcement lives inside the native binary.
FAQ
Do I need to know Cython to use Py2Native?
No. Py2Native uses Cython under the hood but completely hides it. You write plain Python files, and Py2Native generates the necessary Cython bootstrap, compiles to C, and links the native binary automatically.
Can I compile Python code that uses third-party libraries?
Yes. Third-party libraries remain as Python source and are not compiled. Py2Native only compiles your own Python modules. The resulting binary or wheel still imports those libraries normally, so existing dependencies work without modification.
What’s the difference between an executable and a library build?
An executable build (uv run py2native build main.py) produces a standalone native executable that runs your program. A library build (--library) creates a shared library such as .so, .pyd, or .dylib and a Python package that imports from it. That lets you distribute compiled code while keeping the normal Python import interface.
Does Py2Native support license verification for my compiled code?
Yes, with the Pro plugin. You can generate an EC keypair, sign a JWT license, and include the public key in your build. The compiled executable verifies the license at runtime, so you can enforce commercial licensing without relying on external services.
What are the main constraints?
Do not use identical module names across source files, because the compiled output contains a single shared library in library mode. Also, monkey-patching is still possible for third-party libraries that remain as Python source. LGPL libraries remain replaceable by the end user, which is a compliance requirement rather than a build limitation.
Conclusion
Py2Native removes the Cython learning curve from Python native compilation. You write plain Python, run uv run py2native build, and get a native binary or importable shared library. The Cython machinery is still there, but it is managed for you.
If you need to protect proprietary Python code without becoming a build engineer, that is the practical path. Start with the open-source compiler at Py2Native and see how far one command gets you.
Related posts
- Python Code Protection Tools Compared: Which Is Best?
- How to Distribute Python Apps Without Exposing Source Code
- Secure Your Python App with Built-in License Verification