Hi Pythonistas!,

In the previous posts, we looked at how Python compiles conditionals and loops into bytecode. Now, let’s dive into function calls and see how Python manages function execution with bytecode.

**Disassembling a Function with Arguments**

Let’s start with a simple example of a function that takes arguments and returns a value

```
import dis
def multiply(a, b):
return a * b
dis.dis(multiply)
```

**Output**

```
2 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_MULTIPLY
6 RETURN_VALUE
```

Here’s what the bytecode instructions mean:

**LOAD_FAST 0 (a)**: Load the local variable a onto the stack.

**LOAD_FAST 1 (b)**: Load the local variable b onto the stack.

**BINARY_MULTIPLY**: Multiply a and b.

**RETURN_VALUE**: Return the result of a * b.

**Disassembling a Function with a Default Argument**

Next, let’s examine a function with default arguments:

**code**

```
def power(x, exp=2):
return x ** exp
dis.dis(power)
```

**Output**

```
2 0 LOAD_FAST 0 (x)
2 LOAD_FAST 1 (exp)
4 BINARY_POWER
6 RETURN_VALUE
```

In this case, Python disassembles the function in the same way as before, but it internally handles the default argument (exp=2) when the function is called with fewer arguments than required.

In the upcoming post we will learn more about nested functions