Core Python

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.

← Previous Post Next Post →

Recent posts