Disassembling Conditionals and Loops in Python Using dis

Posted by Afsal on 27-Sep-2024

Hi Pythonistas!,

In the previous post, we covered the basics of Python’s bytecode and how to use the dis module. Now, we’ll take it a step further and see how conditionals (if-else statements) and loops (for loops) are compiled into bytecode.

Disassembling an if-else Statement

Let’s start by disassembling a simple function that uses an if-else statement:

import dis

def check_even(x):
    if x % 2 == 0:
        return "Even"
    else:
        return "Odd"

dis.dis(check_even)

Output:

Here’s a breakdown of the bytecode:

LOAD_FAST 0 (x): Load the value of x onto the stack.

LOAD_CONST 1 (2): Load the constant 2 onto the stack.

BINARY_MODULO: Perform the modulo operation (x % 2).

LOAD_CONST 2 (0): Load the constant 0 onto the stack.

COMPARE_OP 2 (==): Compare if x % 2 equals 0.

POP_JUMP_IF_FALSE 18: If the comparison is false, jump to instruction 18 (the else block).

LOAD_CONST 3 ('Even'): Load the string "Even" for the if block.

RETURN_VALUE: Return "Even" if the condition was true.

LOAD_CONST 4 ('Odd'): Load the string "Odd" for the else block.

RETURN_VALUE: Return "Odd" if the condition was false.

Disassembling a Loop

Next, let’s look at how Python disassembles a simple for loop:

code

import dis

def loop_example():
    for i in range(3):
        print(i)

dis.dis(loop_example)

Output:

 4       0 LOAD_GLOBAL          0 (range)

           2 LOAD_CONST           1 (3)

           4 CALL_FUNCTION        1

           6 GET_ITER

     >> 8 FOR_ITER             6 (to 22)

          10 STORE_FAST           0 (i)


  5      12 LOAD_GLOBAL          1 (print)

          14 LOAD_FAST            0 (i)

          16 CALL_FUNCTION        1

          18 POP_TOP

          20 JUMP_ABSOLUTE        4 (to 8)


  4 >>   22 LOAD_CONST           0 (None)

          24 RETURN_VALUE

Here’s how the bytecode works:

LOAD_GLOBAL 0 (range): Load the range() function.

LOAD_CONST 1 (3): Load the constant 3.

CALL_FUNCTION 1: Call the range(3) function.

GET_ITER: Get an iterator from the result of range(3).

FOR_ITER 12: Iterate over the items in the iterator, and if the iterator is exhausted, jump to instruction 22.

STORE_FAST 0 (i): Store the current value of i from the loop.

LOAD_GLOBAL 1 (print): Load the print() function.

LOAD_FAST 0 (i): Load the current value of i onto the stack.

CALL_FUNCTION 1: Call print(i).

POP_TOP: Remove the result of print() from the stack.

JUMP_ABSOLUTE 8: Jump back to instruction 8 to continue the loop.

LOAD_CONST 0 (None): Once the loop is done, load None.

RETURN_VALUE: Return None.

In the next post, we’ll explore how Python handles functions in more detail.