The header is 16 bytes and all its numbers are little-endian. It says which bytecode the file holds, and how Python can tell whether the file is still up to date with its source. The code object starts at offset 16.
| Offset | Size | Field | Meaning |
|---|---|---|---|
| 0 | 2 | magic | Which bytecode the file holds |
| 2 | 2 | 0D 0A | A carriage return and a line feed |
| 4 | 4 | flags | 0, 1 or 3 |
| 8 | 4 | source_mtime | The source’s modification time, when flags is 0 |
| 12 | 4 | source_size | The source’s size in bytes, when flags is 0 |
| 8 | 8 | source_hash | A hash of the source, when flags is 1 or 3 |
Magic number
A 16-bit number, then the bytes 0D 0A. CPython changes the number
whenever its bytecode changes, and only loads a file whose number is its own. The
carriage return and line feed mean that a file copied in text mode, which alters line
endings, no longer has the right magic number.
The final number of each release is shown in bold; the others belong to alpha, beta and release-candidate builds. The descriptions are those of the list in CPython’s source.
| Magic | Bytes | Release | What changed |
|---|---|---|---|
| 3400 | 48 0D | 3.8a1 | Frame block handling moved to the compiler |
| 3401 | 49 0D | 3.8a1 | Added END_ASYNC_FOR |
| 3410 | 52 0D | 3.8a1 | PEP 570 positional-only parameters: the code object gains co_posonlyargcount |
| 3411 | 53 0D | 3.8b2 | Reversed the evaluation order of key: value in dictionary comprehensions |
| 3412 | 54 0D | 3.8b2 | Swapped the position of positional arguments and positional-only arguments in ast.arguments |
| 3413 | 55 0D | 3.8 (from 3.8b4) | Fixed break and continue in finally |
| 3420 | 5C 0D | 3.9a0 | Added LOAD_ASSERTION_ERROR |
| 3421 | 5D 0D | 3.9a0 | Simplified the bytecode for with blocks |
| 3422 | 5E 0D | 3.9a0 | Removed BEGIN_FINALLY, END_FINALLY, CALL_FINALLY and POP_FINALLY |
| 3423 | 5F 0D | 3.9a2 | Added IS_OP, CONTAINS_OP and JUMP_IF_NOT_EXC_MATCH |
| 3424 | 60 0D | 3.9a2 | Simplified the bytecode for *value unpacking |
| 3425 | 61 0D | 3.9 (from 3.9a2) | Simplified the bytecode for **value unpacking |
Flags
A 32-bit number that says how the file is checked against its source, added by PEP 552. Bit 0 means the file holds a hash of the source and not its date and size. Bit 1 means the hash must be compared with the source when the file is loaded.
| Value | Kind | What Python does |
|---|---|---|
| 0 | Date and size | Compares source_mtime and source_size with the source file, and recompiles if either differs |
| 1 | Unchecked hash | Loads the file without looking at the source |
| 3 | Checked hash | Hashes the source and compares it with source_hash |
Bits above bit 1 are reserved, and CPython refuses a file that has any of them set.
The value 2 is not written by CPython, and the explorer treats it as invalid.
py_compile and compileall choose the kind with their
--invalidation-mode option. The interpreter’s
--check-hash-based-pycs option can override the check when the file is
loaded.
Source modification time
At offset 8 when flags is 0: the source file’s modification time when it was compiled, as a 32-bit count of seconds since 1 January 1970. Python compares only the low 32 bits.
Source size
At offset 12 when flags is 0: the size of the source file in bytes, modulo 232.
Source hash
At offset 8 when flags is 1 or 3: eight bytes taking the place of the date and the size. It is a 64-bit SipHash of the source file’s contents, keyed by the magic number, as PEP 552 sets out. The explorer shows it as one little-endian 64-bit number.
File names
Python writes these files to a __pycache__ folder next to the source
and puts the interpreter’s name and version in the file name.
| Source | Compiled by | File |
|---|---|---|
spam.py | CPython 3.8 | __pycache__/spam.cpython-38.pyc |
spam.py | CPython 3.9 | __pycache__/spam.cpython-39.pyc |
spam.py | CPython 3.9 with -O | __pycache__/spam.cpython-39.opt-1.pyc |
spam.py | CPython 3.9 with -OO | __pycache__/spam.cpython-39.opt-2.pyc |
The PYTHONPYCACHEPREFIX environment variable, new in 3.8, makes Python
keep these files in a separate tree. A spam.pyc placed in a folder by
itself, with no source file beside it, can also be imported.