This article uses the Python 2.7.8 source code as an example.
1. Common File Formats in Python
- py file
A Python source code file, which can be modified with a text editor.
- pyc file
The bytecode file generated after Python source code is compiled.
- pyw file
When a pyc file is executed, a console window appears; when a pyw file is executed, it does not. A pyw file is mainly used to run pure GUI (graphical user interface) programs, and running it requires the Pythonw interpreter.
- pyo file
A file produced by compiling Python source code with optimization. Run the command python -O your.py to compile Python source code into a pyo file.
- pyd file
Generally a Python extension module written in another language. A pyd file is a binary file written in the D language (an evolved, synthesized version of C/C++) and produced by compilation.
2. Generating a pyc File from py
If you have write permission on the code directory, a py source file is compiled into a pyc file in two situations:
- A module that is imported generates a pyc file.
- Python code compiled by
py_compilegenerates a pyc file.
What you need to know is that executing a py source file directly does not automatically generate a pyc file.
Single-file compilation:
| |
Directory compilation:
| |
A pyc is a cross-platform binary bytecode file generated by compiling a py source file. The Python interpreter can interpret and execute pyc files, much like a Java virtual machine. At the same time, the content of a pyc is tied to the Python version: different versions of the Python interpreter produce different pyc files after compiling the same py source code. A pyc compiled by Python 2.7 cannot be executed by Python 3.5.
If you execute a pyc across versions, it may raise an error (it says “may” here because the magic number is the same between minor versions, so those can be executed):
| |
There will be further explanation in the content below.
3. The Source Code of the import Implementation
In Python, import generates pyc files. Below, we start from the part of the Python source code that implements import.
- Source location: Python\import.c
| |
When Python executes the import instruction, it looks in the code directory for files with the same name and the suffix pyw, pyo, or pyc. If one is found, it calls the check_compiled_module function to determine whether the timestamp in the header of the pyc file matches the last modified time of the py file. If they match, it loads it directly; otherwise it recompiles and generates a pyc file, and writes the last modified time into the py source.
If none is found, it compiles and generates a pyc file. Below is the source code of check_compiled_module.
- Source location: Python\import.c
| |
The check_compiled_module function determines whether the pyc file needs to be updated by checking whether the last-modified timestamps mtime of the py and pyc files are the same.
- Source location: Python\import.c
| |
When writing the pyc file, a Long variable must also be written, whose content is the last-modified timestamp ftLastWriteTime of the py source file.
4. How pyc Files from Different Versions Are Distinguished
As mentioned earlier, pyc files generated by different Python versions of the interpreter are not the same. So how does the Python interpreter distinguish between them?
In the source code above that generates the pyc file, you can see that PyMarshal_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION); writes the pyc_magic and Py_MARSHAL_VERSION variables into the pyc file. Py_MARSHAL_VERSION specifies the current file format — 2 in version 2.7.8 and 4 in version 3.5.5; pyc_magic specifies version information related to the Python interpreter.
- Source location: Python\import.c
| |
As you can see, the pyc_magic value differs between Python interpreter versions. Defining a new pyc_magic can be used to produce a dedicated Python interpreter, which is also one way to bind a pyc file to a specific Python interpreter.
5. PyCodeObject & PyFrameObject
PyCodeObject is the result actually produced after Python source code is compiled. In other words, any Python source code you write is converted into a PyCodeObject object.
Source location: Include\code.h
| |
The bytecode instructions are stored in the co_code of a PyCodeObjec object. When the Python interpreter executes a sequence of bytecode instructions, that process consists of traversing the entire co_code from beginning to end and executing the bytecode instructions one after another.
Source location: Python\marshal.c
| |
PyMarshal_WriteObjectToFile writes a PyCodeObject object into the pyc file; internally the function calls w_object to store each object appearing in the Python code together with its corresponding TYPE_* marker, for example an int object corresponds to the marker TYPE_INT.
However, when a Python program is running, what its interpreter handles is not a PyCodeObject object but the corresponding stack frame object, PyFrameObject.
- Source location: Include\frameobject.h
| |
The Python interpreter uses the PyInterpreterState structure to maintain the process runtime environment, PyThreadState to maintain the thread runtime environment, and PyFrameObject to maintain the stack frame runtime environment; the three contain one another in that order, as shown below:

The Python interpreter dynamically loads the three structures above into memory and simulates the execution process of the operating system. After the program runs, it first creates each runtime environment, then loads the bytecode in the stack frame and loops through it, interpreting and executing.
