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 + bthe 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 executionA 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 + 1At 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 nameThe 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
flagsThe 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 inputThe 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 innerThe 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", NoneInspect:
print(f.__code__.co_consts)Typical contents:
(None, 1, 'x')Constants may include:
None
booleans
numbers
strings
bytes
tuples of constants
frozensets
nested code objectsThe compiler deduplicates some constants within a code object.
Bytecode instructions load constants by index:
LOAD_CONST 1means “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 0meaning “load the global or builtin name co_names[0].”
Example with attribute access:
def f(obj):
return obj.valuevalue 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 cInspect:
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 cThis array-indexed local access is much faster than dictionary lookup.
23.12 co_freevars and co_cellvars
Closures use two code object fields.
| Field | Meaning |
|---|---|
co_cellvars | Locals captured by nested scopes |
co_freevars | Variables captured from enclosing scopes |
Example:
def outer():
x = 1
def inner():
return x
return innerInspect:
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 valueThe function object supplies:
the actual cell containing 42This 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_flagsExample:
def f(a, b, /, c, *, d):
return a, b, c, dInspect:
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 flagsExample:
def normal():
return 1
def gen():
yield 1
async def coro():
return 1Inspect:
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 * cThe 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 0Maximum 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 dataExample:
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 locationsWithout 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 kindThis 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 object23.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 instructionsFrame:
current execution state
instruction pointer
local variables
evaluation stack
block state
globals
builtins
exception stateExample:
def f(x):
y = x + 1
return yEach 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 = 1123.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 toolingWhen 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 objectThe 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 helpers23.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 behaviorRobust tools should use public APIs:
dis
inspect
types.CodeType.replace
co_positions()
co_lines()
ast
compileAvoid 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.pyConceptual roles:
| Area | Role |
|---|---|
code.h | Code object structure definitions |
codeobject.c | Code object creation and behavior |
compile.c | AST to instruction generation |
assemble.c | Final code object assembly |
flowgraph.c | Control-flow graph processing |
marshal.c | Serialization for bytecode caches |
dis.py | Human-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.