Python’s dis module disassembles bytecode of code objects, functions, methods, and strings. import dis. dis: dis.dis(x, file=None, depth=None) — print human-readable bytecode; x can be function, method, class, code object, string, generator, or coroutine. disassemble: dis.disassemble(code, lasti=-1, file=None) — same but takes a code object. get_instructions: dis.get_instructions(x, first_line=None) → iterator of Instruction named tuples. Instruction: .opname (str), .opcode (int), .arg (int or None), .argval (resolved value), .argrepr (str), .offset (int), .starts_line (int or None), .is_jump_target (bool). code_info: dis.code_info(x) → string summary (name, file, line, argcount, locals, consts, names, etc.). Bytecode: bc = dis.Bytecode(x) — iterable with .first_line, .exception_entries, .codeobj. findlinestarts: dis.findlinestarts(code) → (offset, lineno) pairs. findlabels: dis.findlabels(code.co_code) → list of jump target offsets. stack_effect: dis.stack_effect(opcode, oparg=None, jump=None) → net stack change. opname: dis.opname[opcode] → name string. opmap: dis.opmap["LOAD_CONST"] → opcode int. HAVE_ARGUMENT: opcodes ≥ this value take an argument (CPython 3.6+ all take an arg). Claude Code generates bytecode analyzers, performance profilers, opcode frequency counters, and CPython internals explorers.
CLAUDE.md for dis
## dis Stack
- Stdlib: import dis
- Print: dis.dis(my_function) # human-readable bytecode
- Iter: list(dis.get_instructions(fn)) # Instruction namedtuples
- Info: dis.code_info(fn) # code object summary
- Struct: bc = dis.Bytecode(fn) # codeobj, first_line, iter
- Effect: dis.stack_effect(opcode) # net push/pop delta
dis Bytecode Analysis Pipeline
# app/disutil.py — disassemble, opcode stats, compare, loop detector, perf
from __future__ import annotations
import dis
import io
import types
from collections import Counter
from dataclasses import dataclass
from typing import Any, Callable
# ─────────────────────────────────────────────────────────────────────────────
# 1. Core helpers
# ─────────────────────────────────────────────────────────────────────────────
def disasm_str(obj: Any) -> str:
"""
Return the bytecode disassembly of obj as a string.
Example:
print(disasm_str(lambda x: x * 2))
"""
buf = io.StringIO()
dis.dis(obj, file=buf)
return buf.getvalue()
def instructions(obj: Any) -> list[dis.Instruction]:
"""
Return all Instruction objects for obj.
Example:
for instr in instructions(my_fn):
print(instr.offset, instr.opname, instr.argrepr)
"""
return list(dis.get_instructions(obj))
def code_summary(obj: Any) -> str:
"""
Return dis.code_info() string for obj (name, file, argcount, locals, consts, etc.).
Example:
print(code_summary(my_function))
"""
return dis.code_info(obj)
def first_line(obj: Any) -> int | None:
"""Return the first source line number of obj's bytecode."""
return dis.Bytecode(obj).first_line
# ─────────────────────────────────────────────────────────────────────────────
# 2. Opcode statistics
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class OpcodeStats:
opcode_counts: dict[str, int]
total_instrs: int
n_jumps: int
n_calls: int
n_loads: int
n_stores: int
n_const_loads: int
@classmethod
def from_function(cls, fn: Any) -> "OpcodeStats":
"""
Analyze a function's (or code object's) bytecode.
Example:
stats = OpcodeStats.from_function(my_heavy_fn)
print(stats.top_opcodes(5))
"""
instrs = list(dis.get_instructions(fn))
counts: Counter[str] = Counter(i.opname for i in instrs)
n_jumps = sum(1 for i in instrs if i.opname in dis.hasjabs or i.opname in dis.hasjrel)
n_calls = sum(1 for i in instrs if "CALL" in i.opname)
n_loads = sum(1 for i in instrs if "LOAD" in i.opname)
n_stores = sum(1 for i in instrs if "STORE" in i.opname)
n_const = counts.get("LOAD_CONST", 0)
return cls(
opcode_counts=dict(counts),
total_instrs=len(instrs),
n_jumps=n_jumps,
n_calls=n_calls,
n_loads=n_loads,
n_stores=n_stores,
n_const_loads=n_const,
)
def top_opcodes(self, n: int = 10) -> list[tuple[str, int]]:
"""Return the n most frequent opcodes."""
return Counter(self.opcode_counts).most_common(n)
def summary(self) -> str:
lines = [
f"Instructions: {self.total_instrs}",
f" Jumps: {self.n_jumps}",
f" Calls: {self.n_calls}",
f" Loads: {self.n_loads}",
f" Stores: {self.n_stores}",
f" LoadConst: {self.n_const_loads}",
]
lines.append("Top opcodes:")
for op, cnt in self.top_opcodes(5):
lines.append(f" {op:30s} {cnt}")
return "\n".join(lines)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Code comparison
# ─────────────────────────────────────────────────────────────────────────────
def compare_bytecode(fn_a: Any, fn_b: Any) -> dict[str, Any]:
"""
Compare the bytecode of two callables or code objects.
Example:
result = compare_bytecode(lambda x: x+1, lambda x: x+1)
result["identical"] # True
"""
instrs_a = instructions(fn_a)
instrs_b = instructions(fn_b)
ops_a = [(i.opname, i.argval) for i in instrs_a]
ops_b = [(i.opname, i.argval) for i in instrs_b]
return {
"identical": ops_a == ops_b,
"len_a": len(instrs_a),
"len_b": len(instrs_b),
"a_not_b": [op for op in ops_a if op not in ops_b],
"b_not_a": [op for op in ops_b if op not in ops_a],
}
def instruction_diff(fn_a: Any, fn_b: Any) -> list[str]:
"""
Return a simple line-diff of bytecode between two functions.
Example:
for line in instruction_diff(old_fn, new_fn):
print(line)
"""
lines_a = disasm_str(fn_a).splitlines()
lines_b = disasm_str(fn_b).splitlines()
import difflib
return list(difflib.unified_diff(lines_a, lines_b, fromfile="fn_a", tofile="fn_b", lineterm=""))
# ─────────────────────────────────────────────────────────────────────────────
# 4. Bytecode pattern detectors
# ─────────────────────────────────────────────────────────────────────────────
def has_global_access(fn: Any) -> bool:
"""
Return True if fn reads any global variables (LOAD_GLOBAL instruction).
Globals inside loops are a common performance anti-pattern.
Example:
has_global_access(my_fn) # True if fn uses globals inside
"""
return any(i.opname == "LOAD_GLOBAL" for i in dis.get_instructions(fn))
def count_function_calls(fn: Any) -> int:
"""
Count the number of function call instructions in fn's bytecode.
Higher = potentially more overhead per invocation.
Example:
count_function_calls(compute) # 5
"""
return sum(1 for i in dis.get_instructions(fn) if "CALL" in i.opname)
def find_constant_values(fn: Any) -> list[Any]:
"""
Extract all LOAD_CONST values from a function's bytecode.
Example:
find_constant_values(lambda x: x * 2 + 1) → [..., 2, 1]
"""
return [
i.argval for i in dis.get_instructions(fn)
if i.opname == "LOAD_CONST" and i.argval is not None
]
def uses_comprehension(fn: Any) -> bool:
"""Return True if fn contains any BUILD_LIST/BUILD_SET/BUILD_MAP or comprehension code."""
ops = {i.opname for i in dis.get_instructions(fn)}
return bool(ops & {"BUILD_LIST", "BUILD_SET", "BUILD_MAP", "LIST_APPEND", "SET_ADD", "MAP_ADD"})
def global_writes(fn: Any) -> list[str]:
"""Return names of globals that fn writes to (STORE_GLOBAL)."""
return [i.argval for i in dis.get_instructions(fn) if i.opname == "STORE_GLOBAL"]
# ─────────────────────────────────────────────────────────────────────────────
# 5. Recursive code object traversal
# ─────────────────────────────────────────────────────────────────────────────
def all_code_objects(root: Any) -> list[types.CodeType]:
"""
Recursively collect all code objects nested inside root (lambdas,
comprehensions, nested functions).
Example:
for co in all_code_objects(my_function):
print(co.co_name, co.co_firstlineno)
"""
try:
code = root.__code__ if hasattr(root, "__code__") else root
except AttributeError:
return []
result: list[types.CodeType] = [code]
for const in code.co_consts:
if isinstance(const, types.CodeType):
result.extend(all_code_objects(const))
return result
def full_stats(fn: Any) -> dict[str, Any]:
"""
Aggregate stats across all nested code objects (including list comps, lambdas).
Example:
stats = full_stats(process_records)
print(stats["total_instructions"])
"""
all_instrs = []
for co in all_code_objects(fn):
all_instrs.extend(dis.get_instructions(co))
opcode_counts = Counter(i.opname for i in all_instrs)
return {
"total_instructions": len(all_instrs),
"n_nested_codes": len(all_code_objects(fn)) - 1,
"top_opcodes": opcode_counts.most_common(5),
"has_global_access": any(i.opname == "LOAD_GLOBAL" for i in all_instrs),
"n_calls": sum(1 for i in all_instrs if "CALL" in i.opname),
}
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== dis demo ===")
def multiply(x: int, y: int) -> int:
return x * y
def slow_sum(numbers):
# Uses global 'sum' — each call does LOAD_GLOBAL
total = 0
for n in numbers:
total += n
return total
def fast_sum(numbers):
return sum(numbers)
print("\n--- disasm_str ---")
print(disasm_str(multiply))
print("\n--- code_summary ---")
print(code_summary(multiply))
print("\n--- OpcodeStats ---")
stats = OpcodeStats.from_function(slow_sum)
print(stats.summary())
print("\n--- compare_bytecode ---")
fn1 = lambda x: x + 1
fn2 = lambda x: x + 1
fn3 = lambda x: x + 2
r12 = compare_bytecode(fn1, fn2)
r13 = compare_bytecode(fn1, fn3)
print(f" fn1 vs fn2 identical: {r12['identical']}")
print(f" fn1 vs fn3 identical: {r13['identical']}")
print(f" fn3 not in fn1: {r13['b_not_a']}")
print("\n--- pattern detectors ---")
print(f" has_global_access(slow_sum): {has_global_access(slow_sum)}")
print(f" has_global_access(multiply): {has_global_access(multiply)}")
print(f" count_function_calls(fast_sum): {count_function_calls(fast_sum)}")
print(f" find_constant_values(lambda x: x*2+1): {find_constant_values(lambda x: x * 2 + 1)}")
print(f" uses_comprehension([x*2 for x in range(3)]): ", end="")
print(uses_comprehension(lambda: [x * 2 for x in range(3)]))
print("\n--- full_stats ---")
def process(data):
return [x * 2 for x in data if x > 0]
fs = full_stats(process)
print(f" total_instructions: {fs['total_instructions']}")
print(f" n_nested_codes: {fs['n_nested_codes']}")
print(f" has_global_access: {fs['has_global_access']}")
print(f" top_opcodes: {fs['top_opcodes']}")
print("\n--- instruction_diff ---")
diff = instruction_diff(slow_sum, fast_sum)
for line in diff[:15]:
print(f" {line}")
print("\n=== done ===")
For the bytecode PyPI alternative — the bytecode library (PyPI) provides a mutable bytecode representation with Bytecode, ConcreteInstructions, ControlFlowGraph, and a compiler backend; it enables programmatic bytecode modification and optimization without going through the AST layer; stdlib dis is read-only — use bytecode when you need to generate or mutate bytecode in a build pipeline or JIT system; use dis for profiling, debugging, performance investigation, and education where you only need to read and analyze bytecode. For the ast alternative — ast operates on source text and produces a structured tree that preserves variable names, operators, and expression structure; dis shows the compiled bytecode and reveals how the interpreter will actually execute the code (constant folding, short-circuit evaluation, comprehension code objects); use ast to analyze what the code says; use dis to analyze what the interpreter will do — for example, ast shows the comparison a and b, while dis shows the actual jump-if-false path the bytecode takes. The Claude Skills 360 bundle includes dis skill sets covering disasm_str()/instructions()/code_summary()/first_line() core helpers, OpcodeStats dataclass with total/jumps/calls/loads and top_opcodes() report, compare_bytecode()/instruction_diff() comparison utilities, has_global_access()/count_function_calls()/find_constant_values()/uses_comprehension()/global_writes() pattern detectors, and all_code_objects()/full_stats() recursive code object traversal. Start with the free tier to try bytecode analysis patterns and dis pipeline code generation.