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

    Detailed view of programming code in a dark theme on a computer screen.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python symtable: Analyze Name Scopes

    Learn Python symtable to analyze scopes, locals, globals, parameters, imports, nonlocals, closures, and compiler namespaces.

    Ler mais

    Tempo de leitura: 9 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python tokenize: Read Source Code Tokens

    Learn Python tokenize to read tokens, comments, encodings, indentation, and positions, then transform and rebuild source safely.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python zipapp: Build Executable .pyz Files

    Learn Python zipapp to build .pyz files, define entry points, bundle pure dependencies, handle resources, and distribute secure CLI tools.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python sysconfig: Paths and Build Info

    Learn Python sysconfig to inspect paths, schemes, headers, compiler flags, ABI details, native extensions, and virtual environments.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A person in a hoodie coding on dual monitors, depicting cybersecurity and hacking themes.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python marshal: Internal Binary Format

    Learn Python marshal for internal objects and bytecode, including versions, allow_code, disposable caches, limits, and untrusted-input risks.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A close-up view of fresh, green cucumbers ready for pickling and preservation in Estonia.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python copyreg: Customize Pickle Types

    Learn Python copyreg to customize pickle reducers, version serialized state, avoid global conflicts, and test safely across processes.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026