Python opcode: Explore Bytecode

Published on: August 15, 2026
Reading time: 5 minutes
Monitor with binary code representing Python bytecode opcode instructions

Python opcode covers the numeric codes, readable names, and classifications of CPython bytecode instructions. In current Python documentation, the public opcode collections are documented with dis because the two layers are inseparable: dis decodes compiled code, while opcode tables describe and group the operations.

This subject is useful for analysis tools, debuggers, interpreter research, and bytecode experiments. Bytecode remains a CPython implementation detail. Instructions can change between releases, are not portable to other Python virtual machines, and should never become a permanent application contract.

Opcodes and bytecode

When CPython compiles a function, it produces a code object containing instructions. Each instruction has a numeric opcode, a readable name, and often an argument.

import dis

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

for instruction in dis.get_instructions(add):
    print(
        instruction.opcode,
        instruction.opname,
        instruction.arg,
        instruction.argrepr,
    )

Prefer dis.get_instructions() over parsing co_code manually. It accounts for extended arguments, source positions, inline caches, and version-specific layouts.

Public opcode collections

Current documented collections include opname, opmap, hasarg, hasconst, hasfree, hasname, haslocal, and hasjump, exposed through dis.

import dis

print(dis.opmap['LOAD_CONST'])
print(dis.opname[dis.opmap['RETURN_VALUE']])
print(dis.hasconst[:5])

The internal opcode module contributes to the implementation, but libraries should rely on documented dis interfaces whenever possible.

Map a name to its numeric code

dis.opmap maps operation names to integers.

code = dis.opmap.get('LOAD_FAST')
if code is None:
    raise RuntimeError('Opcode is unavailable in this version')
print(code)

Use get() when supporting multiple Python versions. Operations are regularly added, renamed, merged, or removed. Python 3.14, for example, includes new instructions for borrowed references, templates, monitoring, and common constants.

Map a number to a name

dis.opname is indexed by numeric opcode.

for number, name in enumerate(dis.opname):
    if not name.startswith('<'):
        print(number, name)

Reserved entries can have placeholder names. Not every value is necessarily a real instruction in final executable bytecode.

Detect operations with arguments

dis.hasarg lists operations that use an argument. It is more appropriate than the historical HAVE_ARGUMENT threshold for modern tools.

for instruction in dis.get_instructions(add):
    if instruction.opcode in dis.hasarg:
        print(instruction.opname, instruction.arg)

The meaning of an argument depends on the operation. It may index constants, names, locals, closure cells, comparison types, flags, or jump deltas.

Constants, names, locals, and closures

Specialized collections classify argument usage:

  • hasconst accesses entries in co_consts.
  • hasname refers to names in co_names.
  • haslocal uses local-variable slots.
  • hasfree works with cells and closure variables.
def classify(instruction):
    op = instruction.opcode
    if op in dis.hasconst:
        return 'constant'
    if op in dis.hasname:
        return 'name'
    if op in dis.haslocal:
        return 'local'
    if op in dis.hasfree:
        return 'closure'
    return 'other'

For compiler scope classification before bytecode generation, read the Python symtable guide.

Jumps and control flow

dis.hasjump collects operations with a jump target. Modern CPython uses relative jumps, and target representation has changed across releases.

for instruction in dis.get_instructions(function):
    if instruction.opcode in dis.hasjump:
        print(instruction.opname, instruction.jump_target)

Use Instruction.jump_target instead of calculating targets from bytes. Inline caches, logical labels, and backward-jump rules make raw arithmetic fragile.

Compute stack effect

dis.stack_effect() reports how an instruction changes the evaluation stack.

for instruction in dis.get_instructions(add):
    effect = dis.stack_effect(
        instruction.opcode,
        instruction.arg,
    )
    print(instruction.opname, effect)

For conditional operations, pass jump=True or jump=False to compare paths. The default returns the maximum possible effect.

Specialized instructions

Since Python 3.11, the interpreter can adapt and specialize bytecode at runtime. An Instruction object exposes baseopcode and baseopname for the general operation behind a specialized form.

for instruction in dis.get_instructions(
    function,
    adaptive=True,
):
    print(instruction.opname, instruction.baseopname)

Tools that search only exact specialized names may fail after a function warms up. Grouping by baseopname is often more meaningful.

Inline caches

Some operations reserve cache entries for specialization. Current Instruction.cache_info provides structured information.

for instruction in dis.get_instructions(function):
    if instruction.cache_info:
        print(instruction.opname, instruction.cache_info)

Do not decode populated cache bytes as ordinary opcodes. Their data can resemble arbitrary instructions.

Pseudo-instructions

The compiler uses pseudo-instructions that are replaced or removed before final bytecode is assembled. Modern collections may also include instrumented operations.

When examining executable code, iterate over a real code object with get_instructions() rather than assuming every entry in a global table can appear at runtime.

Comparison operations

dis.cmp_op lists readable comparison names associated with COMPARE_OP. The raw argument can also contain flags, so version-aware tools should use argrepr.

code = compile('a <= b', '<expr>', 'eval')
for instruction in dis.get_instructions(code):
    if instruction.opname == 'COMPARE_OP':
        print(instruction.argrepr)

Count instruction types

from collections import Counter
import dis


def count_operations(obj):
    return Counter(
        instruction.baseopname
        for instruction in dis.get_instructions(obj)
    )

print(count_operations(add))

This report is useful for education and structural comparison, but instruction counts are not reliable performance estimates. The runtime cost depends on objects, caches, calls, memory, and specialization.

Analyze source without running it

A string can be compiled and disassembled without executing the result.

code = compile(
    'result = function(value)',
    '<analysis>',
    'exec',
)
for instruction in dis.get_instructions(code):
    print(instruction.opname)

Compilation of huge or deeply nested input can still consume resources. Apply size and time limits to user-provided source.

Do not build opcode blacklists

Blocking operations such as IMPORT_NAME, LOAD_ATTR, or CALL does not create a secure sandbox. Allowed objects can expose powerful capabilities indirectly, and compiler changes invalidate the list.

Run untrusted code in a separate process or container with limited permissions, CPU, memory, network, filesystem access, and execution time.

Relationship with dis

The Python dis guide covers full disassembly, Bytecode objects, source positions, adaptive caches, and tracebacks. Opcode analysis is the narrower layer focused on instruction tables and categories.

Do not confuse pickle opcodes

The Python pickletools guide also works with opcodes, but those instructions belong to the pickle virtual machine, not CPython bytecode. The formats and security properties are unrelated.

Version compatibility

Never persist numeric opcode values as a long-term protocol. Store the exact Python version and implementation with every report and regenerate analysis when environments change.

Tools that target several interpreters should inspect platform.python_implementation() and reject unsupported virtual machines.

Testing strategy

Test normal functions, closures, classes, comprehensions, generators, coroutines, pattern matching, exceptions, and annotation scopes. Run the suite on every supported Python version.

Exercise cold and adaptive bytecode, cache information, jump targets, and source positions. Avoid rigid snapshots of full textual disassembly; assert semantic properties instead.

  • Use documented dis APIs.
  • Prefer Instruction objects to raw bytes.
  • Group specialized code with baseopname.
  • Account for caches and pseudo-instructions.
  • Record the Python version and implementation.
  • Never use an opcode blacklist as a sandbox.
  • Do not assume numeric stability.
  • Test every target release.

Conclusion

Python opcode is the metadata layer behind CPython instructions. Through collections documented in dis, tools can map names, numeric codes, arguments, jumps, constants, closures, and stack effects.

Use this information for analysis and learning, not as a portable contract. Consult the official opcode collections documentation and the official CPython opcode source.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Developer investigating memory usage with Python tracemalloc
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python tracemalloc: Find Memory Leaks

    Use Python tracemalloc to compare snapshots, locate memory growth, and investigate leaks in long-running applications.

    Ler mais

    Tempo de leitura: 5 minutos
    15/08/2026
    Code with type annotations representing introspection with Python annotationlib
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python annotationlib: Read Annotations

    Learn Python annotationlib in 3.14 to retrieve annotations as values, ForwardRef proxies, or strings while controlling execution risks.

    Ler mais

    Tempo de leitura: 5 minutos
    14/08/2026
    Organized archive binders representing modules imported directly from ZIP files with Python zipimport
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python zipimport: Import from ZIP Files

    Learn Python zipimport to load modules and packages from ZIP archives, work with importers, and protect plugin systems.

    Ler mais

    Tempo de leitura: 5 minutos
    14/08/2026
    Directory diagram representing site-packages paths and Python site module configuration
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python site: Understand Package Paths

    Learn the Python site module to understand site-packages, user site, .pth files, sitecustomize, usercustomize, virtual environments, and startup flags.

    Ler mais

    Tempo de leitura: 6 minutos
    14/08/2026
    Installer icon representing offline pip bootstrap with Python ensurepip
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python ensurepip: Restore pip Offline

    Learn Python ensurepip to install or restore pip offline, choose the environment, script names, upgrade behavior, and avoid system conflicts.

    Ler mais

    Tempo de leitura: 5 minutos
    14/08/2026
    Software package representing package metadata inspected with Python importlib.metadata
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python importlib.metadata: Package Data

    Learn Python importlib.metadata to inspect installed versions, dependencies, files, metadata, entry points, and import-to-distribution mappings.

    Ler mais

    Tempo de leitura: 5 minutos
    14/08/2026