Bytecode Usage
Installation
Install bytecode:
python3 -m pip install bytecode
bytecode
requires Python 3.8 or newer.
Hello World
Abstract bytecode
Example using abstract bytecode to execute print('Hello World!')
:
from bytecode import Instr, Bytecode
bytecode = Bytecode([Instr("LOAD_GLOBAL", (True, 'print')),
Instr("LOAD_CONST", 'Hello World!'),
Instr("CALL", 1),
Instr("POP_TOP"),
Instr("LOAD_CONST", None),
Instr("RETURN_VALUE")])
code = bytecode.to_code()
exec(code)
Output:
Hello World!
Concrete bytecode
Example using concrete bytecode to execute print('Hello World!')
:
from bytecode import ConcreteInstr, ConcreteBytecode
bytecode = ConcreteBytecode()
bytecode.names = ['print']
bytecode.consts = ['Hello World!', None]
bytecode.extend([ConcreteInstr("LOAD_GLOBAL", 1),
ConcreteInstr("LOAD_CONST", 0),
ConcreteInstr("CALL", 1),
ConcreteInstr("POP_TOP"),
ConcreteInstr("LOAD_CONST", 1),
ConcreteInstr("RETURN_VALUE")])
code = bytecode.to_code()
exec(code)
Output:
Hello World!
Setting the compiler flags
Bytecode, ConcreteBytecode and ControlFlowGraph instances all have a flags attribute which is an instance of the CompilerFlag enum. The value can be manipulated like any binary flags.
Setting the OPTIMIZED flag:
from bytecode import Bytecode, CompilerFlags
bytecode = Bytecode()
bytecode.flags |= CompilerFlags.OPTIMIZED
Clearing the OPTIMIZED flag:
from bytecode import Bytecode, CompilerFlags
bytecode = Bytecode()
bytecode.flags ^= CompilerFlags.OPTIMIZED
The flags can be updated based on the instructions stored in the code object using the method update_flags.
Simple loop
Bytecode of for x in (1, 2, 3): print(x)
:
from bytecode import Label, Instr, Bytecode
loop_start = Label()
loop_done = Label()
loop_exit = Label()
code = Bytecode(
[
# Python 3.8 removed SETUP_LOOP
Instr("LOAD_CONST", (1, 2, 3)),
Instr("GET_ITER"),
loop_start,
Instr("FOR_ITER", loop_exit),
Instr("STORE_NAME", "x"),
Instr("LOAD_GLOBAL", (True, "print")),
Instr("LOAD_NAME", "x"),
Instr("CALL", 1),
Instr("POP_TOP"),
Instr("JUMP_BACKWARD", loop_start),
# Python 3.8 removed the need to manually manage blocks in loops
# This is now handled internally by the interpreter
loop_exit,
Instr("END_FOR"),
Instr("LOAD_CONST", None),
Instr("RETURN_VALUE"),
]
)
# The conversion to Python code object resolve jump targets:
# abstract labels are replaced with concrete offsets
code = code.to_code()
exec(code)
Output:
1
2
3
Conditional jump
Bytecode of the Python code print('yes' if test else 'no')
:
from bytecode import Label, Instr, Bytecode
label_else = Label()
label_print = Label()
bytecode = Bytecode([Instr('LOAD_GLOBAL', (True, 'print')),
Instr('LOAD_NAME', 'test'),
Instr('POP_JUMP_IF_FALSE', label_else),
Instr('LOAD_CONST', 'yes'),
Instr('JUMP_FORWARD', label_print),
label_else,
Instr('LOAD_CONST', 'no'),
label_print,
Instr('CALL', 1),
Instr('LOAD_CONST', None),
Instr('RETURN_VALUE')])
code = bytecode.to_code()
test = 0
exec(code)
test = 1
exec(code)
Output:
no
yes
Note
Instructions are only indented for readability.