Skip to main content
Reverse Engineering

Can You Decompile a Python 3.15 `.pyc`? (Tested)

By Mithun··7 min read
Quick Answer

Yes, but not with the usual open-source tools yet. On a real Python 3.15.0 .pyc (magic number 3666), pycdc says Bad MAGIC! and uncompyle6/decompyle3 say Unknown magic number 3666. Python's own marshal + dis still read every string and the full logic, but only when you run them on 3.15: on 3.14, dis crashed. Our free .pyc Decompiler already rebuilds 3.15 source: my test file came back complete, and on six real standard-library modules every function came back, with about 7 in 10 code objects verified exact.

Python 3.15.0 is out, and with every new release comes the same question: can its .pyc files be turned back into source yet? On 24 October 2026 I compiled a test file and took real standard-library modules from the official 3.15.0 build on python.org, then ran every common method against them. Here's exactly what happened.

The test files

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"))
s.py — compiled with Python 3.15.0

I compiled it with 3.15.0's py_compile. The file starts with 52 0e 0d 0a, magic number 3666, which identifies Python 3.15 (3.14 is 3627; the full table is in fix "bad magic number"). For real-world code I also used six modules from 3.15's own standard library (fnmatch, base64, textwrap, json.decoder, functools and difflib), which the official Windows download ships as 3.15 .pyc files.

The open-source decompilers: not yet

$ pycdc s315.pyc
Bad MAGIC!
Could not load file s315.pyc
$ uncompyle6 s315.pyc
# Unknown magic number 3666 in s315.pyc
$ decompyle3 s315.pyc
Unknown magic number 3666 in s315.pyc
Real output on the 3.15 .pyc — pycdc (current master, b428976), uncompyle6 3.9.3, decompyle3 3.9.3

No surprise: pycdc's newest commit is from April 2026, and uncompyle6 and decompyle3 stopped at Python 3.8 and 3.9 years ago. The .pyc isn't damaged or protected; these tools simply don't know version 3666. Support for a new CPython release always arrives late, because every new opcode has to be mapped by hand. For how pycdc behaves on the versions it does support, see the tested pycdc guide.

The standard library still reads it, on 3.15

A .pyc is a 16-byte header plus a marshal-serialized code object, so Python's own marshal and dis modules can read it with no decompiler at all:

import marshal, dis
with open("s315.pyc", "rb") as f:
f.read(16) # 16-byte header
code = marshal.load(f)
def strings(c):
for k in c.co_consts:
if isinstance(k, str):
yield k
elif hasattr(k, "co_consts"):
yield from strings(k)
print(list(strings(code)))
check = next(k for k in code.co_consts if hasattr(k, "co_code"))
for ins in dis.get_instructions(check):
if ins.opname in ("LOAD_CONST", "COMPARE_OP", "RETURN_VALUE", "POP_JUMP_IF_FALSE"):
print(f"{ins.opname:18} {ins.argrepr}")
dis315.py — run it with Python 3.15
['sk-demo-7c1f2a', 'PRO-2026', 'welcome ', 'access denied', 'mithun', 'PRO-2026']
LOAD_CONST 'PRO-2026'
COMPARE_OP bool(==)
POP_JUMP_IF_FALSE to L1
LOAD_CONST 'welcome '
RETURN_VALUE
LOAD_CONST 'access denied'
RETURN_VALUE
Real output on Python 3.15.0

Every string, the license check and both branches, with no decompiler installed.

Use the matching Python version. I ran the same script on Python 3.14: marshal.load still read the 3.15 file and the strings printed, but dis then crashed with IndexError: list index out of range (inside get_argval_argrepr), because 3.15 renumbered some opcodes. Read a 3.15 .pyc with Python 3.15.

Our decompiler on 3.15

Our free .pyc Decompiler detects 3.15 from the magic number and runs its deeper reconstruction pass, which rebuilds each function and checks it by recompiling it and comparing the bytecode. The test file came back complete, both branches included, with every function verified:

# Decompiled with https://pyobfuscate.dev
API_KEY = 'sk-demo-7c1f2a'
def check(user, key):
if key == 'PRO-2026':
return f'welcome {user}'
else:
return 'access denied'
print(check('mithun', 'PRO-2026'))
Our decompiler on the 3.15 .pyc — real output

Real modules are harder. Here's what came back for the six standard-library files (a code object is a function, class body, lambda or comprehension):

3.15 module.pyc sizeCode objects verified exactTime
fnmatch6.6 KB8 of 11 (73%)42 s
base6415 KB20 of 27 (74%)74 s
textwrap12 KB12 of 18 (67%)89 s
json.decoder11 KB8 of 13 (62%)66 s
functools42 KB78 of 92 (85%)3.2 min
difflib45 KB42 of 58 (72%)3.4 min

Every module came back with all its function definitions; the unverified parts are best-effort reconstructions, and the page marks the result as partial when that happens. For comparison I compiled the same source files with 3.14 and 3.15: fnmatch, textwrap and json.decoder verified 90%, 94% and 100% on 3.14, against 73%, 67% and 69% on 3.15. So 3.15 works today, but support is newer: expect a few more partial functions for now.

What this means if you ship 3.15 code

"No decompiler supports my version yet" isn't protection: the strings and logic came straight out with two standard-library calls, and a full decompiler already rebuilds most of it. The usual advice holds:

  1. Obfuscate the source before you compile or ship it. I ran 10 test programs through our Python Obfuscator (both modes, 3 seeds each) and executed all 90 outputs on Python 3.15.0: every one printed exactly the same output as the original. Code that uses syntax new in 3.15, such as lazy import (PEP 810), is supported too (tested 24 October 2026); in the protected output those imports load at startup instead of lazily, and the program behaves the same.
  2. Keep real secrets off the client. A hard-coded key sits in co_consts in plain text on every version; load it from the environment or a backend (protecting API keys in Python).
  3. For the strongest bar, compile to a native module (Cython or Nuitka), so there is no code object to marshal.load at all. See PyArmor vs Nuitka vs Cython for what each one hides.

Decompile a 3.15 .pyc — free

Our free .pyc Decompiler rebuilds source for Python 2.x through 3.15, with the version detected for you.

Open the .pyc Decompiler

Free tools mentioned here

Related guides

Frequently asked questions

Can pycdc decompile Python 3.15?

Not yet. In October 2026, pycdc's current master (commit b428976 from April 2026) rejected a Python 3.15.0 .pyc with 'Bad MAGIC!' because it doesn't know the 3.15 magic number (3666) or its opcodes. uncompyle6 and decompyle3 report 'Unknown magic number 3666'.

What is the Python 3.15 .pyc magic number?

3666. The first four bytes of a Python 3.15 .pyc are 52 0e 0d 0a (3.14 is 3627, bytes 2b 0e 0d 0a). Read the first two bytes as a little-endian integer to identify the version.

How do I decompile a Python 3.15 .pyc file?

Use a decompiler that supports 3.15, such as our free online .pyc Decompiler: in testing it recovered every function of six real 3.15 standard-library modules, with about 7 in 10 code objects verified exact. Without a decompiler, run marshal + dis on Python 3.15 to read the strings and bytecode.

Why does dis crash on a Python 3.15 .pyc?

Because you're running dis from a different Python version. On Python 3.14, marshal.load read a 3.15 .pyc, but dis crashed with 'IndexError: list index out of range' since 3.15 renumbered some opcodes. Run the disassembly with Python 3.15.

Does obfuscated Python code run on Python 3.15?

Ours does: 90 obfuscated test programs (10 programs, both modes, 3 seeds) all produced identical output on Python 3.15.0. Source that uses new 3.15-only syntax such as lazy import (PEP 810) is supported as well; those imports simply load at startup in the protected output.

Keep reading