Skip to main content
Reverse Engineering

How to Decompile a Python `.pyc` File: Every Method, Tested

By Mithun··10 min read
Quick Answer

There are three ways to decompile a .pyc, and which one works depends entirely on the Python version it was built for. 1) An online decompiler (like our browser-based .pyc Decompiler, which runs pycdc) — easiest, no install, handles Python 3.0–3.12. 2) A command-line tooluncompyle6 (≤3.8) or decompyle3 (≤3.9) give near-perfect source; the pycdc CLI covers newer bytecode. 3) From code, with the standard librarymarshal + dis read the code object and its strings on any version, including ones no decompiler supports yet. I tested all three on Python 3.8 through 3.14 below.

A .pyc is compiled Python bytecode — the cached, runnable form of your .py. People ship it thinking the source is gone, but it decompiles back, and there's more than one way to do it. Which method you reach for depends on one thing: the Python version the `.pyc` was compiled for. Get that wrong and every tool throws an error.

So I built one sample script, compiled it with six different Python versions (3.8 through 3.14, using uv to fetch each interpreter), and ran every decompilation method against them. Real commands, real output, and an honest map of what works where. Here's the script every test uses:

The test file

API_KEY = "sk-demo-7c1f2a"
def check(user, key):
if key == "PRO-2026":
return f"welcome {user}"
return "access denied"
print(check("mithun", "PRO-2026"))
sample.py — compiled to a .pyc with each Python version

It has the two things people hope a .pyc hides: a hard-coded key and a bit of logic. Let's see how well it hides them.

Method 1 — an online .pyc decompiler (easiest)

If you just want the source back with zero setup, an online decompiler is the fastest path. Our free .pyc Decompiler runs pycdc (Decompyle++) compiled to WebAssembly, so it decompiles entirely in your browser — the file is never uploaded to a server. Drop the .pyc in and you get source out.

Here's the actual output it produced for the Python 3.12 build of the sample (this is the real pycdc result):

# Source Generated with Decompyle++
# File: input.pyc (Python 3.12)
API_KEY = 'sk-demo-7c1f2a'
def check(user, key):
if key == 'PRO-2026':
return f'''welcome {user}'''
print(check('mithun', 'PRO-2026'))
Our online decompiler (pycdc/WASM) on the 3.12 .pyc — real output

Near-perfect: the key, the function, the PRO-2026 check, the f-string — all recovered. One honest imperfection to notice: it dropped the final return "access denied" line. Decompilers occasionally miss an edge branch on newer bytecode, which is a useful reminder that decompilation is reconstruction, not a byte-perfect inverse.

pycdc powers the online tool because it reads bytecode directly and doesn't need a matching Python interpreter to run — which is why it reaches far newer versions than the pip-based decompilers below.

Method 2 — a command-line decompiler (best fidelity on older bytecode)

For scripting and batch work, the classic tools are uncompyle6 and decompyle3, installed from pip. On the versions they support, their output is essentially the original file. I ran uncompyle6 on the Python 3.8 build (running it under Python 3.8 itself, via uv run):

# uncompyle6 version 3.9.3
# Python bytecode version base 3.8.0 (3413)
# Embedded file name: sample.py
# Size of source mod 2**32: 172 bytes
API_KEY = "sk-demo-7c1f2a"
def check(user, key):
if key == "PRO-2026":
return f"welcome {user}"
return "access denied"
print(check("mithun", "PRO-2026"))
uv run --python 3.8 --with uncompyle6 uncompyle6 sample.pyc

That's byte-for-byte the original, right down to the return "access denied" branch pycdc dropped — plus bonus metadata (the original filename and compile size) that uncompyle6 reads out of the header. The catch is version range: uncompyle6 tops out around Python 3.8, decompyle3 around 3.9. Point either at a 3.11+ .pyc and it won't even start. For newer bytecode you use the `pycdc` CLI (the same engine as the online tool, built from C++).

Rule of thumb: .pyc from Python ≤3.9 → uncompyle6/decompyle3 for the cleanest source. Python 3.10–3.12 → pycdc (CLI or our online tool). Newer than the tools support → Method 3.

Method 3 — from code, with the standard library (works on any version)

Here's the method people forget: you don't strictly need a decompiler at all. A .pyc is a 16-byte header followed by a marshal-serialized code object — the exact thing the interpreter runs. Python's own marshal and dis modules read it on any version, because it's the interpreter's native format. That makes this the universal fallback.

I pointed it at the Python 3.14 build — the one no decompiler above could handle yet:

import marshal, dis
with open("sample.cpython-314.pyc", "rb") as f:
header = f.read(16) # skip the 16-byte .pyc header (3.7+)
code = marshal.load(f) # the module's code object
dis.dis(code) # disassemble the logic
print([c for c in code.co_consts if isinstance(c, str)])
Reading a .pyc with the standard library
string constants recovered:
['sk-demo-7c1f2a', 'PRO-2026', 'welcome ', 'access denied', 'mithun', 'PRO-2026']
# dis excerpt — the license check, in the clear:
LOAD_CONST 3 ('PRO-2026')
COMPARE_OP 88 (bool(==))
LOAD_CONST 1 ('welcome ')
Real output — from the 3.14 .pyc that pycdc rejected

No decompiler, no matching interpreter, no internet — and the API key, the license string, and the exact comparison are all right there. You don't get clean .py back (you get bytecode + constants), but for finding secrets or understanding logic it's often all you need, and it never hits a version wall.

This is why "just ship the .pyc" protects nothing: even when no decompiler supports your Python version, the string literals and control flow are readable straight out of the file with two standard-library calls.

Which tool for which Python version

The single most important thing about decompiling a .pyc is matching the tool to the bytecode version. Here's what I actually observed testing the sample from 3.8 to 3.14:

MethodPython versionsOutputSetup
Our online .pyc Decompiler (pycdc/WASM)3.0–3.12 (3.13 partial, 3.14 not yet)Readable sourceNone — in browser
pycdc CLI (Decompyle++)2.x–3.12+Readable sourceC++ build
decompyle33.7–3.9Near-original sourcepip install
uncompyle62.x–3.8Near-original sourcepip install
marshal + dis (stdlib)Any versionBytecode + constantsBuilt-in

In my run, the online/pycdc engine cleanly decompiled 3.8–3.12, returned a partial result on 3.13 (# WARNING: Decompyle incomplete), and rejected 3.14 with Bad MAGIC!. Decompiler support always lags new Python releases by a while — so for a brand-new version, Method 3 is your bridge until the tools catch up. (Decompiler version support is a moving target; "my version isn't supported yet" is a delay, not a defense — see decompiling a .pyc.)

What this means if you want to protect your code

Every method above recovers either your source or your secrets, so shipping .pyc instead of .py is not protection — it's a cache format. If the goal is to make a .pyc genuinely hard to read, the fix is to change what gets compiled in the first place:

  1. Obfuscate the source before compiling. A decompiler can only give back the source that was compiled — so if you rename every identifier and encrypt the string literals first, the decompiled output is that mangled, encrypted version, not your real code. Our free Python Obfuscator does this at the AST level.
  2. Keep real secrets off the client. As the tests show, a hard-coded key survives every format. Load keys from the environment or your own backend — see how to protect API keys in Python.
  3. For the strongest bar, compile to a native module (.pyd/.so via Cython, or a Nuitka binary) so there's no bytecode to marshal.load at all. See Python to EXE.

More on the full picture in is Python obfuscation secure? and protecting Python source code.

Decompile a .pyc in your browser — free

Our in-browser .pyc Decompiler runs pycdc via WebAssembly. Nothing is uploaded — the file never leaves your machine.

Open the .pyc Decompiler

Free tools mentioned here

Frequently asked questions

How do I decompile a Python .pyc file?

Pick a method by the .pyc's Python version. Easiest: an online decompiler like our browser-based .pyc Decompiler (runs pycdc, handles Python 3.0–3.12, nothing uploaded). For a .pyc from Python ≤3.9, the uncompyle6 or decompyle3 CLI tools give near-original source. For any version — including brand-new ones no decompiler supports yet — read the code object directly with the standard library's marshal and dis modules.

What is the best pyc decompiler?

There's no single best — it depends on the Python version. For modern bytecode (3.10–3.12) pycdc (Decompyle++) is the go-to, and it's what our free online decompiler runs. For Python ≤3.8 uncompyle6 produces the cleanest, near-byte-perfect source. In my tests uncompyle6 even recovered a branch pycdc missed on newer bytecode.

Can you decompile any .pyc back to source?

Almost. Full-source decompilers (pycdc, uncompyle6, decompyle3) cover most Python versions, though support lags the newest releases. When no decompiler supports your version yet, you can still read everything that matters — string literals and control flow — straight from the code object with Python's built-in marshal and dis, so a .pyc never truly hides its contents.

Why does my .pyc decompiler say "Bad MAGIC" or fail to start?

The first four bytes of a .pyc are a magic number identifying the exact CPython version, and each decompiler only knows a range of versions. "Bad MAGIC" (pycdc) or a failure to start (uncompyle6/decompyle3) means the .pyc is from a Python version newer than the tool supports. Use a tool that covers that version, or read the bytecode with marshal + dis in the meantime.

How do I decompile a .pyc without installing anything?

Use an in-browser decompiler such as our .pyc Decompiler — it runs pycdc compiled to WebAssembly, so the file is decompiled locally in your browser and never uploaded. Alternatively, if you have Python installed, marshal + dis are in the standard library and need no extra packages.

Does compiling to .pyc protect my Python source code?

No. A .pyc is cached bytecode that runs without the .py, stores string literals (including API keys) in plaintext, and decompiles back to near-original source. To actually protect code, obfuscate the source before compiling (rename identifiers, encrypt strings), keep secrets off the client, and for the strongest option compile to a native .pyd/.so or a Nuitka binary.

Keep reading