The header is 8 bytes: a magic number that identifies the bytecode, and the modification time of the source file. The code object follows straight after it. CPython writes both as 4-byte little-endian numbers. It writes the magic number, then a time of 0, then the code object, and fills in the real time last, so a file cut off while being written will not match its source.
Magic number
Offset 0, 4 bytes. The low two bytes are the number that names the bytecode, and the
high two are always 0D 0A, a carriage return and a line feed. If the file
is ever handled as text those bytes change, and the magic number no longer matches.
CPython compares the whole 4-byte value and recompiles if it differs.
| Magic number | Bytes in the file | Release |
|---|---|---|
| 60202 | 2A EB 0D 0A | 2.1, 2.1.1, 2.1.2, 2.1.3 |
| 60203 | 2B EB 0D 0A | 2.1 with the -U option |
| 60717 | 2D ED 0D 0A | 2.2 |
| 60718 | 2E ED 0D 0A | 2.2 with the -U option |
The comment in CPython’s Python/import.c gives the rule for the low two
bytes: (YEAR-1995) * 10000 + MONTH * 100 + DAY. So 60202 is 2 February
2001 and 60717 is 17 July 2001. Those are the dates the numbers encode, not release
dates. The source has no note saying what each value changed; what differs between
the releases is on the
Instructions and
Marshal pages.
With the -U option, which made string literals Unicode, CPython adds 1 to
the magic number. The explorer takes either value as the same release.
Source modification time
Offset 4, 4 bytes: the modification time of the .py file that was
compiled, in seconds since 1970. When Python imports the module it compares this with
the source file’s time, and if they are not equal the source is compiled again. The
explorer shows the field as a date and time in UTC.
File names
The compiled file sits next to its source and takes its name with one letter added:
foo.py becomes foo.pyc. With the -O option
Python writes and reads foo.pyo instead. The two kinds of file have the
same layout.