The dis module disassembles Python bytecode, revealing what the interpreter actually executes. Essential for understanding performance, debugging, and learning Python internals.
Basic Disassembly
import dis
def add(a, b):
return a + b
dis.dis(add)
# 2 0 LOAD_FAST 0 (a)
# 2 LOAD_FAST 1 (b)
# 4 BINARY_ADD
# 6 RETURN_VALUEUnderstanding the Output
2 0 LOAD_FAST 0 (a)
^ ^ ^ ^ ^
| | | | |
line# offset opcode arg (name)
- Line number: Source code line
- Offset: Byte offset in bytecode
- Opcode: The instruction
- Argument: Instruction argument
- Name: Human-readable argument interpretation
Comparing Implementations
import dis
# String concatenation
def concat_plus():
return "hello" + " " + "world"
def concat_join():
return " ".join(["hello", "world"])
def concat_fstring():
return f"hello world"
print("=== Plus ===")
dis.dis(concat_plus)
print("\n=== Join ===")
dis.dis(concat_join)
print("\n=== F-string ===")
dis.dis(concat_fstring)Loop Optimization
import dis
def loop_range():
total = 0
for i in range(100):
total += i
return total
def loop_while():
total = 0
i = 0
while i < 100:
total += i
i += 1
return total
dis.dis(loop_range)
# Uses GET_ITER and FOR_ITER (optimized)
dis.dis(loop_while)
# Uses COMPARE_OP and POP_JUMP_IF_FALSE (more instructions)Bytecode Object
import dis
def example(x):
if x > 0:
return x * 2
return 0
# Access bytecode directly
code = example.__code__
print(f"Name: {code.co_name}")
print(f"Arg count: {code.co_argcount}")
print(f"Local vars: {code.co_varnames}")
print(f"Constants: {code.co_consts}")
print(f"Bytecode: {code.co_code.hex()}")
# Get Bytecode object
bytecode = dis.Bytecode(example)
for instr in bytecode:
print(f"{instr.offset:4d} {instr.opname:20} {instr.argrepr}")Common Opcodes
import dis
# LOAD operations
def loads():
x = 1 # LOAD_CONST, STORE_FAST
y = x # LOAD_FAST
z = len # LOAD_GLOBAL
return z(y) # CALL_FUNCTION
# Attribute access
def attrs():
import os
return os.path # LOAD_ATTR
# Comparisons
def compare(a, b):
return a < b # COMPARE_OP
# Subscript
def subscript(lst):
return lst[0] # BINARY_SUBSCRJump Instructions
import dis
def conditional(x):
if x:
return 1
else:
return 0
dis.dis(conditional)
# 2 0 LOAD_FAST 0 (x)
# 2 POP_JUMP_IF_FALSE 8
# 3 4 LOAD_CONST 1 (1)
# 6 RETURN_VALUE
# 5 >> 8 LOAD_CONST 2 (0)
# 10 RETURN_VALUEException Handling
import dis
def with_try():
try:
risky()
except ValueError:
handle()
finally:
cleanup()
dis.dis(with_try)
# Shows SETUP_FINALLY, POP_EXCEPT, etc.List Comprehension vs Loop
import dis
def use_loop():
result = []
for i in range(10):
result.append(i * 2)
return result
def use_comprehension():
return [i * 2 for i in range(10)]
# Comprehension creates a separate code object
dis.dis(use_comprehension)
# Shows MAKE_FUNCTION for the inner comprehensionInstruction Analysis
import dis
import sys
def analyze_function(func):
"""Analyze bytecode of a function."""
bytecode = dis.Bytecode(func)
stats = {
'total_instructions': 0,
'loads': 0,
'stores': 0,
'jumps': 0,
'calls': 0,
}
for instr in bytecode:
stats['total_instructions'] += 1
if instr.opname.startswith('LOAD'):
stats['loads'] += 1
elif instr.opname.startswith('STORE'):
stats['stores'] += 1
elif 'JUMP' in instr.opname:
stats['jumps'] += 1
elif 'CALL' in instr.opname:
stats['calls'] += 1
return stats
def example(items):
total = 0
for item in items:
if item > 0:
total += item
return total
print(analyze_function(example))Code Object Attributes
def example(a, b, c=10):
x = a + b
y = x * c
return y
code = example.__code__
# Important attributes
print(f"co_name: {code.co_name}") # Function name
print(f"co_argcount: {code.co_argcount}") # Positional args
print(f"co_varnames: {code.co_varnames}") # Local variables
print(f"co_names: {code.co_names}") # Global names
print(f"co_consts: {code.co_consts}") # Constants
print(f"co_stacksize: {code.co_stacksize}") # Max stack depth
print(f"co_nlocals: {code.co_nlocals}") # Number of localsDisassemble String
import dis
# Disassemble code string
dis.dis("x = 1 + 2")
# 1 0 LOAD_CONST 0 (3)
# 2 STORE_NAME 0 (x)
# 4 LOAD_CONST 1 (None)
# 6 RETURN_VALUE
# Note: 1 + 2 is constant-folded to 3!Show Code Info
import dis
def example(a, b):
"""Example function."""
return a + b
dis.show_code(example)
# Name: example
# Filename: <stdin>
# Argument count: 2
# Positional-only: 0
# Kw-only arguments: 0
# Number of locals: 2
# Stack size: 2
# Flags: OPTIMIZED, NEWLOCALS, NOFREE
# Constants: 0: None
# Variable names: 0: a, 1: bPerformance Insights
import dis
# Attribute access in loop
def slow():
import math
total = 0
for i in range(1000):
total += math.sqrt(i) # LOAD_GLOBAL + LOAD_ATTR each iteration
return total
def fast():
from math import sqrt
total = 0
for i in range(1000):
total += sqrt(i) # Just LOAD_GLOBAL (faster)
return total
# Compare bytecode in the loopCommon Opcode Reference
| Opcode | Description |
|---|---|
LOAD_FAST | Load local variable |
LOAD_GLOBAL | Load global/builtin |
LOAD_CONST | Load constant |
STORE_FAST | Store to local |
BINARY_ADD | Addition |
COMPARE_OP | Comparison |
POP_JUMP_IF_FALSE | Conditional jump |
CALL_FUNCTION | Function call |
RETURN_VALUE | Return from function |
GET_ITER | Get iterator |
FOR_ITER | Iterate |
Practical: Detect Recursion
import dis
def is_recursive(func):
"""Check if function calls itself."""
code = func.__code__
bytecode = dis.Bytecode(func)
for instr in bytecode:
if instr.opname == 'LOAD_GLOBAL' and instr.argval == func.__name__:
return True
return False
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
def iterative_sum(n):
return sum(range(n))
print(is_recursive(factorial)) # True
print(is_recursive(iterative_sum)) # FalseThe dis module reveals Python's execution model. Use it to understand performance characteristics, verify optimizations, and learn how Python really works under the hood.
React to this post: