Every .pyc file of this format starts with the same 16 bytes of header,
all little-endian. The header says which release wrote the file and how to tell whether
it is out of date. The module’s code object follows at offset 16.
0magic: the number, then0D 0A4 bytes4flags4 bytes8source_mtime, or the first half of source_hash4 bytes12source_size, or the second half of source_hash4 bytes
Magic number
The first two bytes are a 16-bit number, and the next two are always
0D 0A, a carriage return and a line feed. A file copied in text mode has
those bytes changed, so the magic number no longer matches and the file is rejected.
CPython changes the number whenever the bytecode changes in a way an older interpreter cannot run. Python 3.n starts at 2900 + 50n, so 3.12 starts at 3500, and the number goes up by one for each change within the release. A file loads only on a release with exactly the same number. The file does not say which release it came from in any other way.
Every final Python 3.12 release writes 3531, the bytes CB 0D 0D 0A. The
numbers 3500 to 3530 come from the alphas and the first beta; the full list follows
further down.
Flags
A 32-bit field that says how the file is checked against its source. Two bits are defined by PEP 552, and CPython rejects a file with any other bit set.
| Bit | Meaning |
|---|---|
| 0 | Set: the next 8 bytes are a hash of the source. Clear: they are the source’s date and size. |
| 1 | Only with bit 0: the hash is checked against the source when the file is loaded. |
| Value | Kind of file |
|---|---|
| 0 | Checked by the source’s date and size. The usual kind. |
| 1 | Hash-based, not checked when loaded. The file is used even if the source has changed. |
| 3 | Hash-based, checked when loaded. The source is hashed and the file is used only if the hash matches. |
The value 2, bit 1 alone, is never written. CPython’s importer would read such a file
as one checked by date and size, since bit 0 is clear; the explorer turns it down.
py_compile and compileall make hash-based files with their
invalidation mode options. The interpreter’s --check-hash-based-pycs
option (default, always or never) can override
the check when a hash-based file is loaded.
source_mtime
Unsigned 32-bit, at offset 8, when flags is 0. The source file’s modification time in seconds since 1970, as a Unix timestamp, kept to 32 bits. A file is stale if this differs from the source file’s current time.
source_size
Unsigned 32-bit, at offset 12, when flags is 0. The size of the source file in bytes, modulo 232. A file is stale if this differs from the source’s current size.
source_hash
8 bytes at offset 8, when bit 0 of flags is set. A 64-bit SipHash of the source file’s
bytes, keyed by the magic number. importlib.util.source_hash() computes
it. Python’s importer does not recompute it when flags is 1, unless
--check-hash-based-pycs always is given.
Examples
Both files were written by Python 3.12 with py_compile, from a six-byte
source file, x = 1 and a line feed.
00000000 CB 0D 0D 0A 00 00 00 00 00 F1 53 65 06 00 00 00
0x6553F100 (1700000000, 14 November 2023), source size 6 bytes00000000 CB 0D 0D 0A 03 00 00 00 15 22 56 19 17 F5 DF 08
File names and folders
importlib stores a module’s compiled form in a __pycache__
folder beside the source, named with the interpreter’s tag, as PEP 3147 describes.
The tag gives the release, so several versions of Python can share one source folder.
| Source | Compiled |
|---|---|
spam.py, no optimisation | __pycache__/spam.cpython-312.pyc |
spam.py, with -O | __pycache__/spam.cpython-312.opt-1.pyc |
spam.py, with -OO | __pycache__/spam.cpython-312.opt-2.pyc |
A spam.pyc file with no spam.py beside it is loaded as a
module that has no source. The .pyo extension is not used in this format.
All magic numbers
The list is the one kept in Python 3.12’s own source, in the comments of
Lib/importlib/_bootstrap_external.py. The number marked final is the one
every released 3.12 version writes. The others come from alphas and the first beta,
and the release shown is the pre-release that introduced them.
| Magic | Release | What changed |
|---|---|---|
| 3500 | 3.12a1 | Remove PRECALL |
| 3501 | 3.12a1 | YIELD_VALUE’s argument is the stack depth |
| 3502 | 3.12a1 | LOAD_FAST_CHECK; LOAD_FAST no longer checks for NULL |
| 3503 | 3.12a1 | Shrink the LOAD_METHOD cache |
| 3504 | 3.12a1 | Merge LOAD_METHOD back into LOAD_ATTR |
| 3505 | 3.12a1 | Specialisation and a cache for FOR_ITER |
| 3506 | 3.12a1 | Add BINARY_SLICE and STORE_SLICE |
| 3507 | 3.12a1 | Set the line number of the module’s RESUME to 0 |
| 3508 | 3.12a1 | Add CLEANUP_THROW |
| 3509 | 3.12a1 | Conditional jumps only jump forward |
| 3510 | 3.12a2 | FOR_ITER leaves the iterator on the stack |
| 3511 | 3.12a2 | Add the STOPITERATION_ERROR instruction |
| 3512 | 3.12a2 | Remove all unused constants from code objects |
| 3513 | 3.12a4 | Add CALL_INTRINSIC_1; remove STOPITERATION_ERROR, PRINT_EXPR and IMPORT_STAR |
| 3514 | 3.12a4 | Remove ASYNC_GEN_WRAP, LIST_TO_TUPLE and UNARY_POSITIVE |
| 3515 | 3.12a5 | Embed a jump mask in COMPARE_OP’s argument |
| 3516 | 3.12a5 | Add COMPARE_AND_BRANCH |
| 3517 | 3.12a5 | Change YIELD_VALUE’s argument to the exception block depth |
| 3518 | 3.12a6 | Add RETURN_CONST |
| 3519 | 3.12a6 | Modify SEND |
| 3520 | 3.12a6 | Remove PREP_RERAISE_STAR, add CALL_INTRINSIC_2 |
| 3521 | 3.12a7 | Shrink the LOAD_GLOBAL caches |
| 3522 | 3.12a7 | Remove JUMP_IF_FALSE_OR_POP and JUMP_IF_TRUE_OR_POP |
| 3523 | 3.12a7 | Convert COMPARE_AND_BRANCH back to COMPARE_OP |
| 3524 | 3.12a7 | Shrink the BINARY_SUBSCR caches |
| 3525 | 3.12b1 | Shrink the CALL caches |
| 3526 | 3.12b1 | Add instrumentation support |
| 3527 | 3.12b1 | Add LOAD_SUPER_ATTR |
| 3528 | 3.12b1 | Add the LOAD_SUPER_ATTR_METHOD specialisation |
| 3529 | 3.12b1 | Inline list, dict and set comprehensions |
| 3530 | 3.12b1 | Shrink the LOAD_SUPER_ATTR caches |
| 3531 | 3.12b1, final | Add PEP 695 changes |