Skip to content

23. Code Objects

PyCodeObject fields: co_code, co_consts, co_names, co_varnames, co_filename, and how they are used at runtime.

A code object is CPython’s compiled representation of executable Python code.

It contains bytecode and metadata. The interpreter can execute it, but the code object itself does not carry runtime state such as globals, default arguments, closure cells, or bound methods.

For this source:

def add(a, b):
    return a + b

the function object add contains a code object:

code = add.__code__

print(code.co_name)
print(code.co_varnames)
print(code.co_consts)
print(code.co_names)

The function object is runtime state. The code object is executable program data.

23.1 Position in the Compilation Pipeline

Code objects are the output of compilation.

source text
tokenization
parsing
AST
symbol table
compiler
code object
frame execution

A code object is the handoff between the compiler and the interpreter.

The compiler emits code objects.

The evaluation loop executes code objects inside frames.

23.2 Code Object vs Function Object

A function object wraps a code object.

Example:

def f(x):
    return x + 1

At runtime:

function object
    __code__       code object
    __globals__    module globals dictionary
    __defaults__   positional defaults
    __kwdefaults__ keyword-only defaults
    __closure__    closure cells
    __dict__       function attributes
    __name__       function name
    __qualname__   qualified name

The code object contains the compiled body.

code object
    bytecode
    constants
    names
    local variable names
    free variable names
    cell variable names
    filename
    line information
    stack size
    flags

The same code object can theoretically be used by more than one function object.

import types

def f(x):
    return x + 1

g = types.FunctionType(f.__code__, globals(), "g")

print(g(10))

The code is shared. The function wrapper changes the runtime binding.

23.3 Code Objects Are Immutable

Code objects are immutable.

Once created, a code object’s bytecode, constants, variable names, flags, and metadata cannot be changed in place.

This matters for safety and runtime design. Multiple functions or frames may refer to the same code object. If code objects were mutable, changing one could affect currently running code.

To modify code, tools create a new code object.

Modern Python exposes a replace() method:

def f():
    return 1

new_code = f.__code__.replace(co_name="renamed")

This creates a modified copy rather than editing the original object.

23.4 Module Code Objects

A module also has a code object.

Example:

src = """
x = 1
y = 2
print(x + y)
"""

code = compile(src, "example.py", "exec")

This code object represents top-level execution.

It has no function parameters. Its local namespace is usually the module dictionary.

Inspect:

print(code.co_name)
print(code.co_filename)
print(code.co_consts)
print(code.co_names)
print(code.co_varnames)

For module code, co_name is often "<module>".

The interpreter executes this code object with globals and locals. Normal imports, script execution, and exec() all use module-style code objects.

23.5 Expression Code Objects

compile() can also produce expression code objects.

Example:

code = compile("1 + 2", "<input>", "eval")
result = eval(code)

print(result)

An eval code object represents one expression. It returns the expression value.

This differs from exec mode:

compile("x = 1", "<input>", "exec")
compile("1 + 2", "<input>", "eval")

exec mode accepts statements.

eval mode accepts only an expression.

The mode changes both the grammar start point and the generated code object behavior.

23.6 Interactive Code Objects

Interactive mode uses "single" compilation.

Example:

code = compile("1 + 2", "<stdin>", "single")
exec(code)

In interactive mode, expression results may be printed automatically by the display hook.

This is why the REPL behaves differently from script execution.

exec mode:
    execute statements normally

eval mode:
    return expression value

single mode:
    behave like interactive input

The code object records enough information for the interpreter to run the selected mode correctly.

23.7 Nested Code Objects

Nested executable constructs produce nested code objects.

Example:

def outer(x):
    def inner(y):
        return x + y
    return inner

The module code object contains the code object for outer in co_consts.

The outer code object contains the code object for inner in its own co_consts.

Inspect:

def outer(x):
    def inner(y):
        return x + y
    return inner

outer_code = outer.__code__
print(outer_code.co_consts)

for const in outer_code.co_consts:
    if isinstance(const, type(outer_code)):
        print("nested code:", const.co_name)

Nested code objects are constants because they are compiled data embedded in the enclosing code object.

At runtime, MAKE_FUNCTION creates function objects from those code objects.

23.8 co_code

co_code stores bytecode bytes.

Example:

def f(a, b):
    return a + b

print(f.__code__.co_code)

Raw co_code is not pleasant to read directly. Use dis:

import dis

dis.dis(f)

Bytecode is an instruction stream for CPython’s virtual machine.

Each instruction has an opcode and possibly an argument. The exact encoding changes across versions, so tools should use dis rather than hard-coding byte offsets.

23.9 co_consts

co_consts stores constants referenced by bytecode.

Example:

def f():
    return 1, "x", None

Inspect:

print(f.__code__.co_consts)

Typical contents:

(None, 1, 'x')

Constants may include:

None
booleans
numbers
strings
bytes
tuples of constants
frozensets
nested code objects

The compiler deduplicates some constants within a code object.

Bytecode instructions load constants by index:

LOAD_CONST 1

means “load co_consts[1].”

23.10 co_names

co_names stores names used for global lookup, attribute lookup, imports, and similar operations.

Example:

def f(xs):
    return len(xs)

Inspect:

print(f.__code__.co_names)

Likely output:

('len',)

The bytecode uses an index into co_names:

LOAD_GLOBAL 0

meaning “load the global or builtin name co_names[0].”

Example with attribute access:

def f(obj):
    return obj.value

value appears in co_names because attribute names are stored there too.

23.11 co_varnames

co_varnames stores local variable names.

Example:

def f(a, b):
    c = a + b
    return c

Inspect:

print(f.__code__.co_varnames)

Typical output:

('a', 'b', 'c')

Fast local bytecode uses indexes into this tuple.

LOAD_FAST 0    a
LOAD_FAST 1    b
STORE_FAST 2   c

This array-indexed local access is much faster than dictionary lookup.

23.12 co_freevars and co_cellvars

Closures use two code object fields.

FieldMeaning
co_cellvarsLocals captured by nested scopes
co_freevarsVariables captured from enclosing scopes

Example:

def outer():
    x = 1

    def inner():
        return x

    return inner

Inspect:

print(outer.__code__.co_cellvars)

inner = outer()
print(inner.__code__.co_freevars)

Expected shape:

('x',)
('x',)

For outer, x is a cell variable because nested code needs it.

For inner, x is a free variable because it comes from an enclosing scope.

23.13 Closure Cells Are Not Stored in Code Objects

A code object records which free variables it needs. It does not store the actual captured values.

The captured values live in closure cells attached to the function object.

Example:

def make_reader(value):
    def read():
        return value
    return read

f = make_reader(42)

print(f.__code__.co_freevars)
print(f.__closure__)
print(f.__closure__[0].cell_contents)

The code object says:

this function needs a free variable named value

The function object supplies:

the actual cell containing 42

This separation lets the same nested code object be reused with different captured values.

23.14 co_argcount and Argument Metadata

Code objects store argument counts.

Important fields include:

co_argcount
co_posonlyargcount
co_kwonlyargcount
co_varnames
co_flags

Example:

def f(a, b, /, c, *, d):
    return a, b, c, d

Inspect:

code = f.__code__

print(code.co_argcount)
print(code.co_posonlyargcount)
print(code.co_kwonlyargcount)
print(code.co_varnames)

These fields help the function call machinery bind arguments to local variable slots.

Defaults are not stored in the code object. They live on the function object:

def f(x=1):
    return x

print(f.__defaults__)
print(f.__code__.co_consts)

The default value belongs to the function object because defaults are evaluated at function definition time.

23.15 co_flags

co_flags stores execution flags.

Flags describe properties such as:

has *args
has **kwargs
is generator
is coroutine
is async generator
uses nested scopes
future flags

Example:

def normal():
    return 1

def gen():
    yield 1

async def coro():
    return 1

Inspect:

print(normal.__code__.co_flags)
print(gen.__code__.co_flags)
print(coro.__code__.co_flags)

The runtime uses these flags to decide what object to create when a function is called.

A generator function returns a generator object.

A coroutine function returns a coroutine object.

A normal function executes normally and returns a value.

23.16 co_stacksize

co_stacksize records the maximum evaluation stack depth needed by the code object.

Example:

def f(a, b, c):
    return a + b * c

The compiler computes stack usage from bytecode stack effects.

Conceptually:

LOAD_FAST a       stack depth 1
LOAD_FAST b       stack depth 2
LOAD_FAST c       stack depth 3
BINARY_OP *       stack depth 2
BINARY_OP +       stack depth 1
RETURN_VALUE      stack depth 0

Maximum depth: 3.

The frame uses co_stacksize to allocate enough stack storage.

23.17 Source Metadata

Code objects store source metadata.

Important fields include:

co_filename
co_name
co_qualname
co_firstlineno
line table data
position table data

Example:

def f():
    x = 1
    return x

code = f.__code__

print(code.co_filename)
print(code.co_name)
print(code.co_qualname)
print(code.co_firstlineno)

This metadata supports:

tracebacks
debuggers
profilers
coverage tools
inspection
warnings
error locations

Without source metadata, Python execution would still be possible, but diagnostics would be much worse.

23.18 Line Tables and Positions

Code objects map bytecode offsets back to source positions.

You can inspect positions:

def f(x):
    return x + 1

for item in f.__code__.co_positions():
    print(item)

This source mapping supports precise tracebacks and debugging.

Older CPython versions used different line number table formats. Modern versions expose richer position information, including column offsets.

Tools should prefer public methods rather than directly parsing private binary tables.

23.19 Exception Tables

Modern CPython code objects include exception table information.

Exception tables describe which bytecode ranges are protected by exception handlers.

Example:

def f():
    try:
        risky()
    except ValueError:
        recover()

The bytecode is not enough by itself. The interpreter also needs metadata such as:

protected instruction range
handler target
stack depth restoration information
handler kind

This metadata lets CPython implement try, except, finally, and related control flow.

23.20 Code Object Creation from compile()

The compile() built-in creates code objects.

Example:

code = compile("x = 1\n", "<input>", "exec")

ns = {}
exec(code, ns)

print(ns["x"])

For expressions:

code = compile("1 + 2", "<input>", "eval")

print(eval(code))

For AST input:

import ast

tree = ast.parse("x = 1\n")
code = compile(tree, "<ast>", "exec")

compile() runs the same broad pipeline used for files:

source or AST
validation
symbol analysis
bytecode generation
code object

23.21 Executing Code Objects

Code objects can be executed with exec() or eval().

Example:

code = compile("x = 10\n", "<input>", "exec")

globals_dict = {}
locals_dict = {}

exec(code, globals_dict, locals_dict)

print(locals_dict["x"])

exec() supplies runtime namespaces.

The code object itself does not contain those namespaces.

For expression code:

code = compile("x + 1", "<input>", "eval")

print(eval(code, {"x": 41}))

The same code object can execute with different globals.

23.22 Code Objects and Frames

A frame is a running instance of a code object.

Code object:

immutable compiled instructions

Frame:

current execution state
instruction pointer
local variables
evaluation stack
block state
globals
builtins
exception state

Example:

def f(x):
    y = x + 1
    return y

Each call to f creates or uses a frame executing f.__code__.

Multiple calls use the same code object but different frame state.

f.__code__
    shared by all calls

frame for f(1)
    x = 1
    y = 2

frame for f(10)
    x = 10
    y = 11

23.23 Code Objects and marshal

CPython can serialize code objects with marshal.

Compiled .pyc files contain marshalled code objects plus header metadata.

This is implementation-specific. The marshal format is not a stable general-purpose serialization format.

Practical rule:

use pickle or another format for application data
use marshal only for CPython internals or closely related tooling

When Python imports a module, CPython may load a cached .pyc file, read the code object, and execute it instead of recompiling source.

23.24 Code Objects in .pyc Files

A .pyc file stores compiled bytecode cache data.

Conceptually:

.pyc file
    header
        magic number
        invalidation metadata
    marshalled module code object

The module code object may contain nested function and class body code objects in co_consts.

The bytecode cache speeds startup by avoiding repeated parsing and compilation when source has not changed.

But .pyc files are version-specific. Bytecode and marshal formats can change across CPython versions.

23.25 Code Object Security Boundaries

Code objects are executable data.

Running a code object is equivalent to running code.

Example:

code = compile("import os; os.remove('file.txt')", "<input>", "exec")
exec(code)

The code object does not become safe because it has already been compiled.

Security-sensitive systems should not execute untrusted code objects, untrusted source, or untrusted marshalled bytecode.

Compilation is not sandboxing.

23.26 Code Object Introspection

Code objects are useful for introspection.

Example:

def f(a, b=1):
    c = a + b
    return c

code = f.__code__

for name in [
    "co_argcount",
    "co_posonlyargcount",
    "co_kwonlyargcount",
    "co_nlocals",
    "co_stacksize",
    "co_flags",
    "co_consts",
    "co_names",
    "co_varnames",
    "co_freevars",
    "co_cellvars",
]:
    print(name, getattr(code, name))

This supports tools such as:

debuggers
profilers
coverage tools
tracers
decorators
test frameworks
bytecode inspectors
static analysis helpers

23.27 Code Object Replacement

Code objects can be copied with modifications using replace().

Example:

def f():
    return 1

new_code = f.__code__.replace(co_name="g")

This is useful for advanced tools.

But changing code object internals can easily violate assumptions. For example, bytecode, constants, names, stack size, flags, and exception tables must remain consistent.

A broken code object can crash tools, raise confusing errors, or behave incorrectly.

Use replace() for metadata edits or carefully validated bytecode work.

23.28 Code Objects Are Version-Specific

Code object fields and bytecode details change across Python versions.

Examples of version-sensitive areas:

opcode names
opcode arguments
inline cache layout
exception table format
line table format
code object constructor signature
optimization behavior

Robust tools should use public APIs:

dis
inspect
types.CodeType.replace
co_positions()
co_lines()
ast
compile

Avoid assuming that raw bytecode layout remains stable.

23.29 CPython Source Areas

Important CPython source files include:

Include/cpython/code.h
Objects/codeobject.c
Python/compile.c
Python/assemble.c
Python/flowgraph.c
Python/marshal.c
Lib/dis.py
Lib/inspect.py
Lib/types.py

Conceptual roles:

AreaRole
code.hCode object structure definitions
codeobject.cCode object creation and behavior
compile.cAST to instruction generation
assemble.cFinal code object assembly
flowgraph.cControl-flow graph processing
marshal.cSerialization for bytecode caches
dis.pyHuman-readable bytecode inspection

23.30 Minimal Mental Model

Use this model:

A code object is immutable compiled Python code.
It contains bytecode plus metadata.
A function object wraps a code object with runtime context.
A frame executes a code object.
Nested functions, lambdas, classes, comprehensions, and generators have nested code objects.
Constants, names, locals, free variables, and cell variables are stored in code object tables.
The interpreter uses the code object to allocate frames and run bytecode.

Code objects are the compiled artifact that connects CPython’s compiler to its virtual machine.