Skip to Content
Course content

11: How Python Code Is Executed: .py to Bytecode

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're an architect. You draw up a beautiful, high-level blueprint for a house. It shows where the walls go, where the windows are, and how the rooms connect. But if you hand that blueprint to a construction worker and say, "Build this," they can't just "execute" a drawing. They need a set of granular, step-by-step instructions: "Pick up a 2x4," "Apply nail to point A," "Hammer nail."

Python works almost exactly like that. Your .py file is the blueprint. It's written for humans to read. But the computer's processor has no idea what def or if x in list means. There is a hidden middle step where Python translates your blueprint into a set of "assembly instructions" called bytecode.

The Translation Pipeline

When you run a Python script, you aren't actually running the source code. You're triggering a two-stage process. First, the Python compiler (which is built right into the CPython interpreter) parses your code and converts it into bytecode. This isn't machine code (the 1s and 0s the CPU understands), but rather a low-level, platform-independent set of instructions.

To map this back to our analogy:

  • The .py file: This is your blueprint. High-level and readable.
  • The Compiler: This is the foreman who reads the blueprint and writes the granular task list.
  • The Bytecode: This is the task list. It's much simpler than the blueprint, but not yet a physical house.
  • The Python Virtual Machine (PVM): This is the construction crew. They read the task list one by one and actually perform the work on your hardware.

Peeking at the Bytecode

Usually, this happens so fast you never notice it. You might have seen a __pycache__ folder in your project directory; those .pyc files are just Python saving that "task list" so it doesn't have to re-translate the blueprint the next time you run the program. It's a performance optimization, nothing more.

But as an engineer, I don't like "magic." I want to see what's actually happening. Python provides a built-in module called dis (short for disassemble) that lets us peek at the bytecode. Let's look at a specific example. Instead of something trivial, let's look at a function that checks if a number is even or odd:

import dis

def check_parity(n):
    return "Even" if n % 2 == 0 else "Odd"

dis.dis(check_parity)

If you run this, you won't see Python code. You'll see something like this:


  2           0 LOAD_FAST                0 (n)
              2 LOAD_CONST               1 (2)
              4 BINARY_MODULO
              6 LOAD_CONST               2 (0)
              8 COMPARE_OP               1 (==)
             10 POP_JUMP_IF_FALSE       14
             12 LOAD_CONST               3 ('Even')
             14 RETURN_VALUE
             16 LOAD_CONST               4 ('Odd')
             18 RETURN_VALUE

Look at how the PVM thinks. It doesn't see a "conditional expression." It sees a stack. It LOAD_FAST (pushes n onto the stack), LOAD_CONST (pushes 2 onto the stack), and then BINARY_MODULO (pops both, calculates the remainder, and pushes the result back). I find this fascinating because it reveals why certain operations are faster than others in Python; the fewer bytecode instructions the PVM has to process, the faster your code runs.

Why This Matters to You

You might be wondering, "Why do I care about this if I'm just writing Python?" Because understanding that Python is a "compiled-to-bytecode" language explains why you get SyntaxError before a single line of your code actually runs. The compiler is failing to build the task list. If the blueprint is illegible, the foreman can't even start writing the instructions, so the construction crew (the PVM) never even gets called to the site.




📋 Practical Task

Analyzing the Bytecode of a Power Function

Your goal is to use the dis module to understand how Python handles exponentiation versus multiplication. This will help you visualize how the Python Virtual Machine processes different operators.

The Requirements:

  1. Create two functions: calc_square_mul(n) which returns n * n, and calc_square_pow(n) which returns n ** 2.
  2. Use the dis.dis() function to print the bytecode for both functions.
  3. Compare the output. Look for the specific BINARY_... instruction used in each function.
  4. Identify which function results in more bytecode instructions and write a brief comment in your code explaining the difference you see in the operation names (e.g., BINARY_MULTIPLY vs BINARY_POWER).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.