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 configuring a Python HTTPS server and TLS certificate
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: Build a Local HTTPS Server in Python

    Learn Python HTTPSServer for local HTTPS services, TLS certificates, threaded handling, testing, and practical security limits.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Protected files representing secure TAR extraction with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: Extract TAR Safely

    Learn Python tarfile extraction_filter for safer TAR extraction, path validation, links, permissions, and resource limits.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Python code showing warnings controlled with catch_warnings
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: Capture Python Warnings in Tests

    Learn Python catch_warnings to capture, test, and control warnings with focused filters and safe temporary scopes.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Python code representing persistent pickle references
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: Serialize External References

    Learn Python pickle persistent_id for stable external references, validation, security, performance, and long-term compatibility.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Binary code representing Python buffers and memory views
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: Count Values Without Buffer Copies

    Learn Python memoryview.count to count bytes and values in buffers without copies, with formats, limits, and practical safety.

    Ler mais

    Tempo de leitura: 5 minutos
    06/10/2026
    Python code and type annotations
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: Avoid Circular Imports in Annotations

    Learn Python annotationlib to inspect deferred annotations, avoid circular imports, and build safer runtime tooling.

    Ler mais

    Tempo de leitura: 5 minutos
    06/10/2026