CodeNebula
Python🚀Advancedadvanced

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

snippet.py
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

bytecode_dis.py
# 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")
Run command
python3 bytecode_dis.py