dis module
The dis module disassembles Python bytecode — it converts the compiled bytecode that CPython actually executes into a human-readable format. When you want to understand what your code is really doing, or why something performs the way it does, bytecode is where you look.
Bytecode is CPython’s internal representation. The dis module reads bytecode defined in the interpreter and exposes it for analysis. This is a CPython-specific tool: other Python implementations (PyPy, MicroPython) may use different bytecode entirely.
dis.dis() — Disassemble a Function or Code Object
The main entry point:
import dis
def add_one(x):
return x + 1
dis.dis(add_one)
1 RESUME 0
2 LOAD_FAST 0 (x)
LOAD_CONST 1 (1)
BINARY_OP 0 (+)
RETURN_VALUE
For a module or class, dis.dis() recursively disassembles all functions or methods:
dis.dis(sys) # disassembles all functions in the sys module
If called with no argument, disassembles the last traceback:
dis.dis()
dis.Bytecode — The Bytecode Analysis Object
Bytecode wraps code for detailed inspection:
bc = dis.Bytecode(add_one)
for instr in bc:
print(instr.opname, instr.argval)
Bytecode accepts several keyword arguments. first_line sets which line number to report as the first line in the output. current_offset marks a specific instruction as the “current” one (displayed with -->). show_caches (3.13+) displays inline cache entries used by the interpreter to specialize bytecode. adaptive shows the specialized bytecode that CPython generates at runtime. show_offsets includes instruction offsets. show_positions (3.14+) includes source positions.
from_traceback() constructs a Bytecode object from a traceback and sets current_offset to the instruction that raised the exception:
try:
something_that_fails()
except Exception:
dis.dis(distb())
dis.get_instructions() — Iterate Over Instructions
Returns an iterator of Instruction named tuples:
for instr in dis.get_instructions(add_one):
print(instr.opname, instr.argrepr)
Each Instruction has these attributes:
| Attribute | Description |
|---|---|
opname | Human-readable opcode name (e.g., LOAD_FAST) |
opcode | Numeric opcode value |
arg / oparg | Numeric argument (or None) |
argval | Resolved argument value (e.g., a variable name) |
argrepr | Human-readable argument description |
offset | Bytecode index of this instruction |
starts_line | True if this starts a source line |
line_number | Source line number (or None) |
is_jump_target | True if another instruction jumps here |
jump_target | Target offset if this is a jump instruction |
cache_info | Cache entry information (3.13+) |
dis.code_info() and dis.show_code()
code_info() returns a formatted multi-line string with details about a code object:
print(dis.code_info(add_one))
Name: add_one
Filename: <stdin>
Argument count: 1
Positional-only arguments: 0
Kw-only arguments: 0
Non-default kwargs: 0
First line: 1
Free variables: ()
Cell variables: ()
...
show_code() is a convenience wrapper that prints the same information to a file (or sys.stdout):
dis.show_code(add_one) # print to stdout
dis.show_code(my_function, file=f) # print to file object f
dis.distb() — Disassemble a Traceback
Disassembles the top-of-stack function from a traceback, marking the failing instruction:
import traceback
dis.distb()
# same as: dis.distb(sys.exc_info()[2])
dis.findlinestarts() — Line Start Offsets
Yields (offset, lineno) pairs for each line start in bytecode:
list(dis.findlinestarts(add_one.__code__))
# [(0, 1), (2, 2)]
Line numbers can be None in bytecode that does not map to source lines (added in 3.13).
dis.findlabels() — Jump Target Offsets
Finds all bytecode offsets that are jump targets — places another instruction can jump to:
dis.findlabels(add_one.__code__)
# returns a list of offsets
dis.stack_effect() — Compute Stack Effects
Computes how an opcode changes the stack depth. This is useful for understanding bytecode complexity:
dis.stack_effect(dis.opmap['LOAD_FAST']) # 1 (pushes one value)
dis.stack_effect(dis.opmap['POP_TOP']) # -1 (removes one value)
dis.stack_effect(dis.opmap['BINARY_OP'], oparg=0) # -1 (pops 2, pushes 1)
The jump parameter controls whether to use the jump or non-jump stack effect for conditional jumps:
# For a JUMP_IF_TRUE instruction:
dis.stack_effect(opcode, oparg, jump=True) # stack if jumping
dis.stack_effect(opcode, oparg, jump=False) # stack if not jumping
Command-Line Interface
dis can be run as a script:
python -m dis mymodule.py # disassemble a file
python -m dis -O mymodule.py # show instruction offsets
python -m dis -C mymodule.py # show inline caches (3.13+)
python -m dis -S mymodule.py # show specialized bytecode (3.14+)
python -m dis -P mymodule.py # show source positions (3.14+)
Common Bytecode Operations
Knowing a few opcodes helps you read dis output:
LOAD_FAST/LOAD_CONST— push a local or constant onto the stackSTORE_FAST— pop the top of stack into a localCALL— call a functionBINARY_OP— perform a binary operation (+,-, etc.)COMPARE_OP— perform a comparisonJUMP_IF_TRUE/JUMP_IF_FALSE— conditional jumpsPOP_JUMP_IF_TRUE/POP_JUMP_IF_FALSE— pop and jumpRETURN_VALUE— return from a functionRESUME— marks the start of a function (3.11+)
See Also
- python-bytecode — understanding
.pycfiles and how Python compiles source to bytecode - debugging-with-pdb — using the Python debugger, which builds on inspection utilities
- python-gil-explained — using bytecode analysis to reason about Python’s thread model