# 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. ```python # 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, '', '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