Skip to main content

Code Objects

A CodeObject is a built-in Python type representing compiled executable code, such as a function body or class definition.

Overview

Code objects contain:
  • Bytecode instructions - The executable code
  • Associated metadata - Constants, names, variable info
  • Context information - Source locations, exception table

Structure

Key Fields

The PyCodeObject C struct is defined in Include/cpython/code.h. Important fields:
  • co_code_adaptive - Bytecode array (since 3.11, was co_code bytes object before)
  • co_consts - Tuple of constants used (numbers, strings, etc.)
  • co_names - Tuple of global/attribute names
  • co_varnames - Tuple of local variable names
  • co_cellvars - Tuple of cell variable names (for closures)
  • co_freevars - Tuple of free variable names (from outer scopes)
  • co_exceptiontable - Exception handling table
  • co_linetable - Source code location table
  • co_stacksize - Maximum stack depth needed
  • co_firstlineno - First source line number

Bytecode Array

Since Python 3.11, bytecode is stored directly in the code object as co_code_adaptive:
This change:
  • Saves an allocation (no separate bytes object)
  • Allows mutation for specialization
  • Enables inline caches
The array is declared with size [1] but actually extends to the required length. This is a C flexible array member pattern.

Creation and Initialization

Code objects are typically created by the compiler:
  1. Compiler generates instruction sequence
  2. _PyAssemble_MakeCodeObject() creates PyCodeObject (Python/assemble.c)
  3. _PyCode_Quicken() initializes inline caches (Python/specialize.c)

Quickening

Quickening initializes adaptive instruction caches:

Immutability

Code objects are nominally immutable:
  • Most fields are read-only after creation
  • Exceptions: co_code_adaptive, _co_monitoring (runtime info)
  • Immutable fields are used for hashing and comparison

Sharing Code Objects

Code objects can be safely shared:
  • Between function objects
  • Across threads
  • When cached on disk (.pyc files)
Mutable fields (co_code_adaptive) use appropriate synchronization.

Source Code Locations

The co_linetable field maps bytecode offsets to source locations.

Why Source Locations Matter

When an exception occurs:
  1. Interpreter adds traceback entry for current frame
  2. tb_lineno computed from co_linetable via PyCode_Addr2Line()
  3. Full location (line, column, end line, end column) available

Location Table Format

The locations table is a compressed format storing 4-tuples:

Accessing Locations

From Python:
From C:

Locations Table Encoding

The locations table uses variable-length encoding to save space. See the format specification for details.
Each entry consists of:
  • Length (in code units)
  • Start line delta
  • End line delta
  • Start column
  • End column
Multiple encoding forms optimize for common cases:

Variable-Length Integers

Locations table uses two integer encodings: Unsigned (varint):
Signed (svarint):

Serialization

Code objects are serialized using the marshal protocol.

.pyc Files

Compiled modules are cached as .pyc files:
  1. Source code compiled to code object
  2. Code object marshalled to bytes
  3. Magic number + timestamp/hash + marshalled code written to .pyc
  4. On import, .pyc loaded and unmarshalled

Magic Number

The magic number identifies bytecode version:
Changing bytecode format requires updating the magic number in:
  • Lib/importlib/_bootstrap_external.py
  • PC/launcher.c (Windows launcher)

Code Object Methods

Python API

Replacement

Execution

Code objects are executed by the interpreter:
Defined in Python/ceval.c.

Example: Examining Code Objects

Output: