Python bytecode decompiler for Python 1.0 through 3.15.
This is the Python port of depyo.js. The JavaScript version is the canonical implementation and is ~4–5× faster; this port exists so you can hack on decompilation logic in Python without a Node runtime.
- Fixture parity with JS baseline: 1183 / 1216 byte-identical (97.3%) across the full fixture suite (Python 1.0–3.15).
- Port-only bugs: 0. The 33 remaining diverging fixtures fail identically on the JS side — they're shared bug-for-bug parity, not port defects.
- Bytecode coverage: Python 1.0 through 3.15.
pip install depyoOr from source:
pip install -e .depyo my_script.pyc
# → writes my_script.py next to the inputAll JS CLI flags are supported (--asm, --debug, --raw, --dump, --stats, --strict, --py-version, --basedir, --out, --skip-source-gen, --skip-path, --marshal, --marshal-scan, --file-ext). Run depyo --help for the full list.
Measured across 1216 .pyc fixtures, 10 passes each:
| Runtime | ms/file | pyc/s | Relative |
|---|---|---|---|
| Node.js (V8) — depyo.js | 0.47 | 2143 | 1.0× |
| CPython 3.12 | 2.48 | 402 | 5.3× slower |
| PyPy 3.10 | 3.87 | 259 | 8.3× slower |
This port is ~4–5× slower than the JS original. Honest disclosure: if you need throughput, use the JS version. If you want to read and modify decompilation logic in idiomatic Python, use this one.
PyPy does not help — it's actually 35% slower than CPython on this workload. The fixtures are short-lived (fresh PycReader/PycDecompiler per file), so the JIT never warms up; hot paths are also heavy on hasattr/getattr/isinstance against polymorphic shapes, which PyPy doesn't specialize well.
Decompiles .pyc / .pyo from Python 1.0 through 3.15, including:
- All control flow (if/elif/else, while, for, try/except/finally, with, match/case)
- Comprehensions (list, set, dict, generator, async)
- Functions, classes, decorators, async def, coroutines
- PEP 695/696 generic type syntax (3.12+)
- PEP 750 template strings (3.14+)
MIT — see LICENSE.
Copyright (c) 2011–2026 Sergey Kuznetsov.