Site Tools


python-bytecode

Python bytecode

Python bytecode is the intermediate representation Python compiles source code into before execution. The dis module lets you inspect bytecode instructions, showing exactly what operations Python executes. Understanding bytecode helps you optimize hot paths, debug performance issues, and understand Python's execution model.

Use bytecode inspection to understand how Python executes your code, identify performance bottlenecks, or verify compiler optimizations.

Example

This example shows bytecode inspection with the dis module.

# run: python3 bytecode.py
# description: inspecting bytecode with dis module
 
import dis
 
def simple_add(a, b):
    return a + b
 
print("Bytecode for simple_add:")
dis.dis(simple_add)
 
print("\n" + "="*60 + "\n")
 
# More complex function
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)
 
print("Bytecode for factorial:")
dis.dis(factorial)
 
print("\n" + "="*60 + "\n")
 
# List comprehension vs loop
def using_loop():
    result = []
    for i in range(10):
        result.append(i * 2)
    return result
 
def using_comprehension():
    return [i * 2 for i in range(10)]
 
print("Loop version:")
dis.dis(using_loop)
 
print("\nList comprehension version:")
dis.dis(using_comprehension)
 
print("\n" + "="*60 + "\n")
 
# Inspect bytecode of expressions
code = "x = 1; y = 2; z = x + y"
print(f"Bytecode for: {code}")
dis.dis(compile(code, '<string>', 'exec'))
 
print("\n" + "="*60 + "\n")
 
# Inspect bytecode object
func = lambda x: x * 2
print(f"Code object: {func.__code__}")
print(f"Bytecode: {func.__code__.co_code}")
print(f"Variable names: {func.__code__.co_varnames}")
print(f"Constants: {func.__code__.co_consts}")
print(f"Argument count: {func.__code__.co_argcount}")
 
print("\nDisassembly:")
dis.dis(func)

Common patterns

Inspecting functions:

  • dis.dis(func): print bytecode for function
  • dis.dis(lambda x: x+1): works on lambdas too
  • dis.dis(code_object): works on code objects

Inspecting classes and methods:

  • dis.dis(ClassName.method): method bytecode
  • dis.dis(ClassName): all methods in class (Python 3.11+)

Bytecode for strings:

  • compile(code_str, 'filename', 'exec'): compile string to code object
  • dis.dis(code_object): inspect compiled code

Code object introspection:

  • func.__code__: code object for function
  • code.co_varnames: local variable names
  • code.co_consts: constants used in function
  • code.co_names: names used (globals, attributes)
  • code.co_argcount: number of arguments
  • code.co_code: raw bytecode bytes

Common bytecode instructions:

  • LOAD_CONST: load constant onto stack
  • LOAD_FAST: load local variable
  • LOAD_GLOBAL: load global variable
  • BINARY_OP: binary operation (+, -, *, etc.)
  • CALL_FUNCTION: call function
  • RETURN_VALUE: return from function
  • POP_TOP: discard top of stack
  • JUMP_IF_FALSE_OR_POP: conditional jump

Performance insights:

  • Local variable access is faster than global (LOAD_FAST vs LOAD_GLOBAL)
  • Function calls are expensive (visible in bytecode)
  • List comprehensions are optimized (separate code path)
  • Loop variables are looked up every iteration

When to inspect bytecode:

  • Understanding performance characteristics
  • Debugging mysterious behavior
  • Verifying compiler optimizations work
  • Learning how Python executes code
  • Investigating performance regressions

Limitations:

  • Bytecode is implementation detail; can change between Python versions
  • Some optimizations happen at interpretation time (C-level)
  • JIT compilers (PyPy, etc.) generate different bytecode
  • Most optimization is better done at algorithm level, not bytecode

Python version differences:

  • Bytecode format changes with Python versions
  • Don't rely on specific bytecode in version-sensitive code
  • Use sys.version_info if you need version-specific behavior
python-bytecode.md · Last modified: by 127.0.0.1