PYC File Documentation
Summary
A compiled Python bytecode file stores the CPython interpreter’s intermediate representation of a .py source module, cached so the code loads faster on the next run. It is not source and not a native executable: it holds a versioned 16-byte header plus a marshalled code object. Its MIME type is application/x-python-code. Python generates .pyc files automatically inside a __pycache__ folder, and they are safe to delete because the interpreter regenerates them.
Technical details
| Feature | Value |
|---|---|
| Full name | Compiled Python bytecode file |
| File extension | .pyc |
| MIME type | application/x-python-code |
| Format type | Binary CPython bytecode (marshalled code object) |
| Developer | Python Software Foundation (CPython) |
| Introduced | Early CPython (1990s); current header since Python 3.7 (2018) |
| Header size | 16 bytes (since Python 3.3) |
| Magic number | 4 bytes, version-specific; e.g. A7 0D 0D 0A for Python 3.11 |
| Constant bytes | Bytes 2–3 are always 0D 0A (\r\n) |
| Byte order | Little-endian (header words and marshal) |
| Invalidation | Timestamp+size, or source hash (PEP 552, Python 3.7+) |
| Payload | Marshalled code object (bytecode, constants, names) |
| Serialisation | CPython marshal module |
| Standard/Spec | PEP 552; dis and importlib documentation |
| Open standard | Partial — CPython-specific, documented but version-bound |
| Cache location | __pycache__/module.cpython-311.pyc |
| Version-portable | No — tied to the exact CPython version that wrote it |
| Legacy variant | .pyo (optimized) merged into .pyc in Python 3.5 |
| Related extensions | .py, .pyw, .pyd, .pyz, .whl |
| Specification | peps.python.org/pep-0552/ |
What is a PYC file?
PYC stands for Python compiled. It is the file in which CPython, the reference Python interpreter, caches the bytecode it compiles from a .py source module. Bytecode is a low-level, platform-independent instruction stream for the Python virtual machine, one level below readable source and well above native CPU instructions. The mechanism dates to the early days of CPython in the 1990s; the header layout used today was fixed by PEP 552 in Python 3.7 (2018), which made .pyc files reproducible.
The point of a .pyc is purely to save work. The first time a module is imported, CPython parses the source, compiles it, and writes the result to a .pyc. On every later run, if the source is unchanged, the interpreter loads the cached bytecode and skips the parse-and-compile step, so the program starts faster. It never runs faster once executing: the same bytecode is interpreted either way. A .pyc is a cache, not a distributable binary, and it is bound to one exact interpreter version, so it is nothing like a standalone EXE.
The 16-byte header: magic, flags and invalidation
Every .pyc opens with a 16-byte header made of four 32-bit little-endian words. PEP 552 defined the current shape; before Python 3.7 the header was 12 bytes (three words). The layout is:
offset size field
0 4 magic number (version tag; bytes 2-3 are always 0x0D 0x0A)
4 4 bit field (flags; PEP 552 invalidation mode)
8 4 word 3 (source mtime, OR low 32 bits of a hash)
12 4 word 4 (source size, OR high 32 bits of a hash)
16 ... marshalled code object
The magic number in bytes 0–3 identifies the CPython version that produced the file. It is not a constant: it is bumped on almost every feature release and whenever the bytecode set changes. Python 3.11, for example, writes A7 0D 0D 0A. The trailing 0D 0A (a carriage return and line feed) is deliberate: it is the same trick PNG uses in its signature, so a file corrupted by a text-mode transfer that rewrites line endings fails the magic check instead of loading garbage. When the running interpreter’s expected magic does not match the file’s, Python discards the cache and recompiles from source.
The second word is a bit field that PEP 552 added. Its lowest bit selects the invalidation strategy. When bit 0 is clear (the default), the file is timestamp-based: word 3 holds the source’s last-modified time (a Unix timestamp) and word 4 holds the source’s size in bytes. On import, CPython compares those against the current .py; if either differs, the cache is stale and gets rebuilt. When bit 0 is set, the file is hash-based: words 3–4 hold a 64-bit SipHash of the source computed with a hard-coded key. Bit 1, the check_source flag, then decides whether the interpreter re-reads and re-hashes the source on every import (checked) or trusts the cache blindly (unchecked). Hash-based pyc files exist so that build systems get deterministic output that does not depend on filesystem timestamps.
The marshalled code object at offset 16
From byte 16 to the end of the file is a single marshalled code object. marshal is CPython’s internal serialisation format for code objects and the basic Python types they reference. It is not pickle: it is deliberately simple, versioned with the bytecode, and not meant to be secure or cross-version stable. Each marshalled value begins with a one-byte type code (sometimes with the high bit set to signal that the object should be added to an interning reference table), followed by that type’s data.
The top-level object is the module’s code object, and it nests further code objects for every function, class body, comprehension and lambda inside it. A code object carries the fields the virtual machine needs to run it:
co_code the raw bytecode (opcode + argument bytes)
co_consts tuple of constants (numbers, strings, nested code objects)
co_names global and attribute names referenced
co_varnames local variable names
co_argcount number of positional parameters
co_flags bit flags (generator, coroutine, *args, **kwargs ...)
co_firstlineno first source line, plus a line-number table
co_filename original source path (kept for tracebacks)
You can inspect all of this without any third-party tool. The standard-library dis module disassembles a code object into human-readable opcode names, and importlib.util.MAGIC_NUMBER tells you the magic the running interpreter expects. Loading a .pyc by hand is a matter of skipping the 16-byte header and calling marshal.loads on the rest.
The bytecode: opcodes, oparg and the CACHE slot
The bytes inside co_code are the instruction stream. Since Python 3.6 the encoding is wordcode: every instruction is exactly two bytes, one opcode byte and one argument byte. Opcodes below a fixed threshold (HAVE_ARGUMENT, 90) take no meaningful argument; those at or above it use the argument byte. When one byte is not enough to hold an argument, the assembler prefixes one or more EXTENDED_ARG instructions that shift additional bits into the operand, so a single logical argument can span several two-byte units.
The instruction set is a stack machine. LOAD_FAST pushes a local onto the value stack, LOAD_CONST pushes an entry from co_consts, BINARY_OP pops two operands and pushes a result, CALL invokes a callable, and RETURN_VALUE pops the top of stack as the function’s result. Because the set is defined per version, this is the deepest reason a .pyc is not portable: a byte that means one operation in 3.10 can mean something else, or not exist, in 3.12.
Python 3.11 introduced the specialising adaptive interpreter, and with it the CACHE pseudo-opcode. A CACHE entry occupies a normal two-byte slot in the stream but executes as a no-op; it reserves inline space that certain opcodes use to store per-call-site specialisation data at runtime (for example, a resolved attribute offset). This is why a raw hex view of 3.11+ bytecode shows apparent gaps: they are inline cache slots, not padding.
The __pycache__ directory and tagged filenames
Before Python 3.2, a module’s cache sat next to its source as module.pyc, which caused collisions when the same source was run under different interpreters. PEP 3147 moved caches into a __pycache__ subdirectory and tagged each filename with the implementation and version, for example module.cpython-311.pyc. The tag (cpython-311) means a CPython 3.11 cache and a PyPy or 3.12 cache can coexist in the same folder without overwriting one another. The interpreter that owns a tag is the only one that will load the matching file.
One case skips __pycache__: when you run a directory or zip as a script, or point the interpreter straight at a .pyc, the file can live anywhere. The compileall module and the py_compile module let you pre-build caches for a whole tree, which is common in packaging so that the first import after install is already warm.
Recovering .py source from a .pyc
The most-searched task for a .pyc is getting readable source back after the original .py is lost. This is decompilation, not a clean conversion, and it is lossy by nature: comments, blank lines and exact formatting were never stored, and some control-flow shapes do not round-trip. Two families of tools exist. uncompyle6 and its successor decompyle3 reconstruct fairly accurate source for Python up to roughly 3.8. For 3.9 and newer, where those decompilers stop keeping up, pycdc (Decompyle++) is the usual fallback, though its accuracy on the latest releases is limited. Whatever the tool, always run the recovered source to confirm it behaves the same as the bytecode.
A weaker but always-available option is disassembly. Because dis ships with Python, any .pyc that the matching interpreter can load can be turned into an opcode listing even when no decompiler supports its version. That is enough to understand what a function does, if not to reproduce the original text.
Frequently asked questions
Why does a .pyc have a version-specific magic number?
Because the bytecode instruction set and the marshal format change between CPython releases. The magic number in bytes 0–3 encodes the exact version, and the interpreter refuses any cache whose magic does not match its own, then recompiles from source. This is why a 3.11 .pyc will not load under 3.12.
Is it safe to delete __pycache__ and .pyc files?
Yes. They are an automatic performance cache. Python regenerates each .pyc from its .py the next time the module is imported, so removing them only costs a one-off recompile. Nothing in your program depends on the cache existing.
What is the difference between a timestamp-based and a hash-based pyc?
It is decided by bit 0 of the header’s bit field. Timestamp-based caches store the source’s modification time and size and are invalidated when either changes. Hash-based caches (PEP 552) store a 64-bit SipHash of the source instead, giving deterministic, timestamp-independent builds; a second flag controls whether the hash is re-checked on every import.
References
- PEP 552 — Deterministic pycs
- Python docs — dis (bytecode disassembler and opcode reference)
- Python docs — importlib (import system and cache invalidation)
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.