CPython Internals — Bytecode and `dis`
Python · Advanced · Run with python3
Explanation
CPython compiles Python source to bytecode (`.pyc` files), which the CPython virtual machine executes. The `dis` module disassembles bytecode into human-readable instructions. Each instruction has an opcode and optional argument. `code.co_consts`, `co_varnames`, `co_names` store constants, local variable names, and global names. Understanding bytecode explains Python performance characteristics: attribute lookups (`LOAD_ATTR`) are expensive; local variables (`LOAD_FAST`) are fast; function calls have non-trivial overhead.
Real-World Scenario
Profiling shows a tight loop is slow. Disassembling with `dis.dis` reveals 8 `LOAD_ATTR` instructions per iteration — a cached local reference (`fast = obj.method`) replaces them with 1 `LOAD_FAST`, cutting the loop time by 30%. ```python import dis import marshal def factorial(n: int) -> int: result = 1 for i in range(2, n + 1): result *= i return result print("=== Bytecode for factorial ===") dis.dis(factorial) print(f"\nConstants: {factorial.__code__.co_consts}") print(f"Locals: {factorial.__code__.co_varnames}") print(f"Stack size:{factorial.__code__.co_stacksize}") # .pyc file format note import py_compile, pathlib, importlib.util import tempfile, os with tempfile.NamedTemporaryFile(suffix=".py", mode="w", delete=False) as f: f.write("x = 1 + 2\n") tmp = f.name py_compile.compile(tmp, doraise=True) print(f"\nCompiled to: {importlib.util.cache_from_source(tmp)}") os.unlink(tmp) ```
Quick Snippet
import dis
import marshal
def factorial(n: int) -> int:
result = 1
for i in range(2, n + 1):
result *= i
return result
print("=== Bytecode for factorial ===")
dis.dis(factorial)
print(f"\nConstants: {factorial.__code__.co_consts}")
print(f"Locals: {factorial.__code__.co_varnames}")
print(f"Stack size:{factorial.__code__.co_stacksize}")
# .pyc file format note
import py_compile, pathlib, importlib.util
import tempfile, os
with tempfile.NamedTemporaryFile(suffix=".py", mode="w", delete=False) as f:
f.write("x = 1 + 2\n")
tmp = f.name
py_compile.compile(tmp, doraise=True)
print(f"\nCompiled to: {importlib.util.cache_from_source(tmp)}")
os.unlink(tmp)Full Example
# Compile: python3 bytecode_dis.py
# Output:
# Bytecode for factorial printed
import dis
def factorial(n: int) -> int:
result = 1
for i in range(2, n + 1):
result *= i
return result
print("=== Bytecode for factorial ===")
dis.dis(factorial)
print(f"\nConstants: {factorial.__code__.co_consts}")
print(f"Locals: {factorial.__code__.co_varnames}")
print(f"Stack size: {factorial.__code__.co_stacksize}")
# Compare optimized vs unoptimized code
def slow_concat(items):
result = ""
for item in items:
result = result + str(item) # Creates new string each iteration
return result
def fast_concat(items):
return "".join(str(item) for item in items)
print("\n=== slow_concat instruction count ===")
instructions = list(dis.get_instructions(slow_concat))
print(f" {len(instructions)} instructions")
print("\n=== fast_concat instruction count ===")
instructions = list(dis.get_instructions(fast_concat))
print(f" {len(instructions)} instructions")
import timeit
n = 1000
items = range(n)
slow = timeit.timeit("slow_concat(items)", globals=locals(), number=100)
fast = timeit.timeit("fast_concat(items)", globals=locals(), number=100)
print(f"\nspeedup: {slow/fast:.1f}x faster with join")
python3 bytecode_dis.py