Compile Python to .pyd / .so with Cython (Tested): Faster & Harder to Reverse
Cython compiles your Python (.py or .pyx) through C into a native extension module — a .pyd on Windows, .so on Linux/macOS. There's no .py or .pyc to decompile, so it's far harder to reverse than obfuscated bytecode, and it runs much faster. In my test, a compiled module ran a numeric loop 56× faster and shipped as a 35 KB binary — but string literals still leak, so it protects logic, not secrets.
Every other technique on this site works on bytecode — obfuscation rearranges it, decompilers rebuild source from it. Cython sidesteps that entirely: it turns your Python into C and compiles it to a native machine-code module. There's no .pyc in the shipped file, so the decompilers that beat .pyc (uncompyle6, pycdc) have nothing to work with. It's the strongest protection short of a commercial DRM runtime.
I actually built one — Cython 3.0.12 + MSVC on Python 3.11 — and measured everything below. Real commands, real output, and the honest limits.
What Cython compiles to
Cython takes Python source and transpiles it to C, then a normal C compiler builds that into a platform-specific extension module: yourmodule.cp311-win_amd64.pyd on Windows, yourmodule.cpython-311-x86_64-linux-gnu.so on Linux. You import it exactly like a .py module — the difference is what ships. A .py is source; a .pyc is decompilable bytecode; a .pyd/.so is native machine code with no bytecode to recover.
Here's the module I compiled — a fake license check plus a compute loop, the classic "protect this" case:
SECRET_KEY = "PYOB-2026-PRO-9f83kd"def check_license(key):return key == SECRET_KEYdef compute(long long n):cdef long long total = 0, ifor i in range(n):total += (i * i) % 97return total
The cdef long long type declarations are the Cython part — they tell the compiler to use raw C integers instead of Python objects, which is where the speed comes from. (Plain .py files compile too, but untyped code stays object-based and gains far less.)
Building it (real commands)
You need Cython (pip install cython) and a C compiler — MSVC Build Tools on Windows, gcc/clang on Linux/macOS. A three-line setup.py drives the build:
from setuptools import setupfrom Cython.Build import cythonizesetup(ext_modules=cythonize("liccore.pyx", language_level=3))
[1/1] Cythonizing liccore.pyxbuilding 'liccore' extensioncl.exe /c /O2 ... /Tcliccore.c /Fo...\liccore.objlink.exe /DLL ... /OUT:...\liccore.cp311-win_amd64.pydFinished generating code
My ~10-line .pyx expanded to a 6,800-line, 260 KB `liccore.c`, which compiled to a 35 KB `liccore.cp311-win_amd64.pyd`. That .pyd is the only file you ship — the .pyx, .c, and .py stay on your machine.
It runs like a normal module — and much faster
Import and call it exactly like Python:
>>> import liccore>>> liccore.check_license("wrong")False>>> liccore.check_license("PYOB-2026-PRO-9f83kd")True>>> liccore.compute(10_000_000)480000219
For the speed test I ran the identical loop in pure Python and in the compiled module — same input, same output (480000219), so it's a fair comparison:
| compute(10,000,000) | Time | Result |
|---|---|---|
| Pure Python | 891 ms | 480000219 |
Cython .pyd | 16 ms | 480000219 |
| Speedup | 56× | identical |
One real gotcha I hit: my first attempt typed the loop var as cdef int i, and i * i silently overflowed the 32-bit int for large i — the compiled result differed from Python's (which uses arbitrary-precision ints). Switching to cdef long long fixed it. C types are fast because they're fixed-width; that also means they can overflow where Python wouldn't. Choose types carefully.
The protection: no bytecode to decompile
This is the payoff. The tools that make .pyc "not protection" — decompilers that rebuild near-original source — need bytecode. A .pyd/.so has none; it's compiled machine code. Point uncompyle6 or pycdc at it and there's simply nothing to decompile. Recovering logic from a native binary means reading disassembled x86/ARM — orders of magnitude harder than reading recovered Python.
Honest limit — Cython does NOT encrypt strings. My module's secret is compiled to native code, but the string literal is still embedded verbatim. A raw byte search finds it instantly:
$ grep -a -o "PYOB-2026-PRO-9f83kd" liccore.cp311-win_amd64.pydPYOB-2026-PRO-9f83kd
So compilation hides logic (the algorithm, the control flow) but not data (hard-coded keys, license strings, URLs). Same rule as every other technique: keep real secrets off the client — load them from the environment or a server. See how to protect API keys in Python.
The catches
- You need a C toolchain — MSVC on Windows, gcc/clang on Linux/macOS. This is the main friction; it's why you can't truly "compile Python to
.pydonline" in a browser. - The binary is platform- and version-specific — my file is
cp311-win_amd64: CPython 3.11, 64-bit Windows only. You must build one per OS × Python version you support (CI matrix builds handle this). - C-type gotchas — the overflow above is real; untyped
.pycompiles but barely speeds up, and aggressive typing changes semantics. Type the hot paths, test the output. - Not a licensing system — it stops casual source theft, not a determined reverse-engineer with a disassembler, and it enforces no license on its own.
Where it fits vs the alternatives: Nuitka compiles your *whole app* to a binary (see Nuitka vs PyArmor); PyInstaller only *bundles* bytecode and is extractable (PyInstaller vs Nuitka); Cython is the sweet spot for compiling the *sensitive modules* of an otherwise-normal Python project.
The strongest stack: obfuscate, then compile
Compilation and obfuscation aren't rivals — they defend different things, so stack them. Obfuscate the source first (rename identifiers, encrypt string literals so even a byte search yields nothing), *then* Cython-compile the result to a .pyd/.so. Now an attacker faces native machine code and, even if they extract strings, finds only encrypted blobs. That combination — obfuscated logic with no bytecode to decompile and no plaintext strings — is about as far as client-side Python protection goes.
Obfuscate first, then compile — free
Cython hides your logic but not your strings. Run your source through our Python Obfuscator first — rename + encrypt string literals — then Cython-compile the result for native protection with nothing left in plaintext.
Open the Python ObfuscatorFree tools mentioned here
Related guides
Frequently asked questions
How do I compile Python to a .pyd or .so?
Install Cython (pip install cython) and a C compiler (MSVC on Windows, gcc/clang on Linux/macOS), write a three-line setup.py that calls cythonize() on your module, and run python setup.py build_ext --inplace. It transpiles your Python to C and compiles a native .pyd (Windows) or .so (Linux/macOS) you import like a normal module.
Does compiling Python with Cython protect the source code?
It protects the logic. A .pyd/.so is native machine code with no bytecode, so .pyc decompilers (uncompyle6, pycdc) can't recover source from it — far stronger than shipping .py or .pyc. But Cython does not encrypt string literals: hard-coded keys and license strings are still embedded in plaintext and recoverable with a byte search, so keep real secrets off the client.
How much faster is Cython than pure Python?
It depends on typing. In my test, a typed numeric loop over 10 million iterations ran 56× faster as a compiled .pyd (16 ms vs 891 ms) with an identical result. Untyped Python compiled with Cython gains far less — the speed comes from cdef C-type declarations on the hot paths.
Can I compile Python to .pyd online in a browser?
Not really — Cython needs a C compiler and produces a platform- and Python-version-specific binary (e.g. cp311-win_amd64), which a browser can't build. The practical online workflow is to obfuscate the source first in a tool like ours, then Cython-compile locally or in CI. For distributing a single file, see our convert-Python-to-exe guide.
Is Cython better than PyInstaller or Nuitka for protection?
Different jobs. PyInstaller only bundles your bytecode into an exe and it's extractable, so it's not protection on its own. Nuitka compiles the whole app to a binary. Cython is ideal for compiling the sensitive modules of an otherwise-normal Python project to native .pyd/.so while the rest stays plain Python.