Python dis: Understand Bytecode

Published on: August 27, 2026
Reading time: 6 minutes
A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.

The dis module disassembles Python code objects and displays the bytecode instructions executed by the interpreter. It helps explain how expressions, functions, loops, comprehensions, exceptions, and calls are compiled. It is also useful for teaching, debugging tools, performance investigation, and comparing Python releases.

Bytecode is an implementation detail. Instructions, arguments, offsets, inline caches, and optimizations can change between releases, including minor versions. Never build business logic, persistent formats, or security boundaries around one exact instruction sequence unless the runtime version is strictly constrained and tested.

Disassemble a function

dis.dis() accepts functions, methods, classes, modules, source strings, and code objects.

import dis


def add(a, b):
    return a + b


dis.dis(add)

The output shows offsets, instruction names, arguments, and useful operand representations.

Compile an expression

Dynamically compiled code can also be inspected.

code = compile("result = x * 2", "example.py", "exec")
dis.dis(code)

compile() does not execute the source, but untrusted source should still be handled inside resource limits.

Code objects

Functions expose a __code__ object containing bytecode, constants, names, variables, and metadata.

code = add.__code__
print(code.co_varnames)
print(code.co_consts)
print(code.co_names)

These fields are intended for introspection and can evolve.

Programmatic instructions

dis.get_instructions() returns Instruction objects and is more reliable than parsing formatted text.

for instruction in dis.get_instructions(add):
    print(
        instruction.offset,
        instruction.opname,
        instruction.arg,
        instruction.argval,
    )

Use named fields rather than columns from human output.

The Instruction object

An instruction can contain opcode name, numeric argument, resolved value, printable argument, offset, source positions, and jump-target information.

Not every field applies to every opcode. Handle missing values.

Bytecode class

dis.Bytecode provides an iterable interface and formatting helpers.

bytecode = dis.Bytecode(add)
for instruction in bytecode:
    print(instruction.opname)

print(bytecode.dis())

This is useful when an analysis tool needs to keep configuration and code together.

Lines and source positions

Instructions can be associated with source lines and more precise source ranges. Modern Python stores detailed position metadata.

Use it for diagnostics, but accept missing positions in generated or transformed code.

Offsets

Disassembly options can display offsets explicitly. They help interpret jumps and exception tables.

Offsets are not stable identifiers and should not be compared across Python versions or builds.

Load instructions

Constants, names, globals, locals, and attributes use different load operations.

Compare global and local access:

x = 10

def use_global():
    return x


def use_local(x):
    return x

Bytecode visualizes the difference, but language-level scope rules remain the authoritative explanation.

Binary operations

Arithmetic expressions load operands and apply operations.

def calculate(a, b):
    return (a + b) * 2

Modern Python may use one general instruction with an argument identifying the particular binary operation.

Constant folding

The compiler can evaluate selected constant expressions ahead of time.

def example():
    return 2 + 3

Disassembly may contain only the final constant. Do not assume every apparently constant expression will always be folded.

Loops

A for loop normally obtains an iterator, requests successive values, and uses jumps.

def total(items):
    result = 0
    for item in items:
        result += item
    return result

Studying the flow helps teach iteration, although optimization details can change.

Conditions and jumps

if, while, boolean expressions, and short-circuit behavior use jumps.

is_jump_target identifies instructions that can receive control flow.

for instruction in dis.get_instructions(function):
    if instruction.is_jump_target:
        print("target", instruction.offset)

Relative jump details

Jump representation and offset units have changed across Python versions. Use argval and current APIs instead of manually decoding historical byte layouts.

Analysis tools should declare supported releases.

Function calls

Calls involve loading a callable and arguments plus instructions for preparation and execution.

The exact sequence varies for methods, keyword arguments, and adaptive specialization.

Comprehensions

List, set, and dictionary comprehensions can produce nested code objects.

def double_evens(values):
    return [x * 2 for x in values if x % 2 == 0]

dis.dis() can display nested code objects, revealing the implicit function-like component.

Generators

Functions containing yield use instructions and flags for suspension and resumption.

Do not manipulate generator state through bytecode internals. Use public APIs.

async and await

Coroutines and asynchronous generators generate bytecode for waiting, sending, and resuming.

The form changes significantly between versions, so treat it as a study of the current runtime.

Exception handling

Exception handling uses metadata and instructions that have changed substantially through CPython’s evolution.

Do not search only for historical opcodes to identify try blocks. Use current metadata and APIs.

Exception tables

Modern code objects can store compact exception tables rather than placing all information directly in the instruction stream.

Disassembly output exposes useful details, but the internal encoding is not a stability contract.

Adaptive interpreter

CPython can specialize instructions at runtime according to observed object types. This improves performance without changing source code.

Disassembly can show original or specialized instructions depending on options and execution history.

adaptive option

Current APIs can display specialized instructions when available.

dis.dis(function, adaptive=True)

Run a function repeatedly before inspection if you want to observe warm specialization, but results need not be identical across systems.

show_caches

Inline caches can be displayed with show_caches=True.

dis.dis(function, show_caches=True)

These caches are performance internals. Never modify them or use them for functional decisions.

Runtime changes

Specialization means observed bytecode can differ after warm-up. A comparison tool must control whether adaptive code and caches are shown.

Record Python version, options, and execution count.

stack_effect

dis.stack_effect(opcode, oparg) calculates an instruction’s effect on the evaluation stack.

import dis
import opcode

effect = dis.stack_effect(opcode.opmap["LOAD_CONST"], 0)
print(effect)

Conditional jumps can require a branch-specific calculation.

Stack analysis

Adding stack effects linearly is insufficient in a graph containing jumps. Correct analysis propagates stack heights through basic blocks and verifies convergence.

Use a specialized verifier for serious bytecode validation.

opcode module

The opcode module provides maps and opcode categories.

import opcode

print(opcode.opmap.get("RETURN_VALUE"))

Numeric values change. Use names and maps from the current runtime.

Different Python implementations

dis describes bytecode supported by its runtime, especially CPython. Other implementations can use different execution representations.

Do not generalize CPython details into language guarantees.

Performance analysis

Bytecode can reveal repeated loads, calls inside loops, and allocation patterns. Still, visual inspection is not performance measurement.

Use timeit, cProfile, and representative workloads. One fewer instruction may have no meaningful impact.

Microbenchmarks

Compare alternatives with warm-up, repetition, isolation, and noise control.

Adaptive specialization makes steady-state measurement especially important.

Security limitations

The presence or absence of one opcode does not prove code safe or dangerous. Aliases, imports, object behavior, and dynamic features defeat simple allowlists.

Never create a sandbox by validating bytecode and then calling exec(). Use process isolation and a genuinely restricted language.

Untrusted code objects

Do not load code objects from untrusted marshal or pickle payloads into a critical process merely to disassemble them.

Even non-executing analysis should be isolated and resource-limited for hostile inputs.

Bytecode diffs

When comparing versions, normalize offsets, caches, and unstable fields. Compare instruction names and semantic arguments without demanding byte-for-byte identity.

Separate language changes from implementation-only differences.

Command line

The module can disassemble a source file through the command line.

python -m dis module.py

This is useful for quick inspection and learning.

Combine with AST

The AST shows high-level structure while dis shows post-compilation instructions. Comparing both layers helps explain compiler transformations.

See Python ast.

Combine with tokenize

tokenize preserves comments and source spelling, which do not survive into bytecode.

See Python tokenize for the lexical layer.

Testing analysis tools

Run the suite on every supported Python version. Include simple functions, closures, generators, async code, comprehensions, exceptions, and pattern matching.

Use version-specific fixtures when expected opcodes differ.

Common mistakes

Common failures include assuming opcodes are stable, parsing formatted disassembly text, comparing offsets across versions, ignoring adaptive caches, optimizing without measurement, treating bytecode validation as a sandbox, generalizing CPython to all implementations, and loading untrusted code objects.

Conclusion

dis shows how Python compiles and executes code at the bytecode level. Use get_instructions() for programmatic analysis, control cache and adaptive options, and always record the runtime version.

Treat bytecode as a moving implementation detail and profile before optimizing. Consult the official dis documentation and the opcode documentation.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Developer working with UTC timestamps and Python calendar.timegm
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    calendar.timegm: Convert UTC to Unix Timestamps

    Learn Python calendar.timegm to convert UTC date tuples into Unix timestamps safely and avoid local-time conversion bugs.

    Ler mais

    Tempo de leitura: 5 minutos
    14/09/2026
    Programmer analyzing code to identify file MIME types with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    mimetypes.guess_file_type: Detect MIME Types

    Learn Python mimetypes.guess_file_type for MIME detection in paths, URLs, uploads, and HTTP responses with safe fallbacks.

    Ler mais

    Tempo de leitura: 5 minutos
    13/09/2026
    Server components representing isolated Python interpreters running in parallel
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    InterpreterPoolExecutor: True Parallelism in Python

    Learn Python InterpreterPoolExecutor for CPU-bound tasks, isolated interpreters, true parallelism, and safer concurrency design.

    Ler mais

    Tempo de leitura: 6 minutos
    13/09/2026
    Microprocessor representing CPUs available to a Python process
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    os.process_cpu_count: Count Available CPUs

    Learn Python os.process_cpu_count to size workers according to the CPUs actually available to the process.

    Ler mais

    Tempo de leitura: 4 minutos
    12/09/2026
    Laptop with digital code representing SQLite BLOB data
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    sqlite3.Blob: Incremental BLOB Reads and Writes

    Learn Python sqlite3.Blob for incremental BLOB reads and writes, lower memory use, and safer binary data handling in SQLite.

    Ler mais

    Tempo de leitura: 5 minutos
    12/09/2026
    Statistical analysis for Python random.binomialvariate
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    random.binomialvariate: Simulate Binomial Outcomes

    Learn Python random.binomialvariate to simulate successes, validate probabilities, and analyze binomial scenarios with practical examples.

    Ler mais

    Tempo de leitura: 5 minutos
    11/09/2026