# Functions

Functions are crucial when it comes to organise our workflow. In Python they are defined with the `def`

keyword which must be followed by the function name and the parenthised list of formal parameters. The statements that form the body of the function start at the next line and need to be indented. For example, we can encapsulate the computation of the $n$-$th$ Fibonacci number just as we did in the previous section.

```
def fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a+b
return a
```

If we now call the function,

```
>>> fibonacci(16)
987
>>> fibonacci(32)
2178309
```

If you are familiar with `C`

you will realise that you don’t have to specify the type of variable neither in the function input nor in the output. Indeed, Python functions **can return any object**.

Another feature that makes Python functions dazzle is that they **can return multiple values** simply put in a tuple. The way to indicate it is by commas, pretty straightforward. For instance,

```
def two_fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a+b
return a, b
```

Again, calling the function,

```
>>> res = two_fibonacci(16)
>>> res
(987, 1597)
```

Once we have set the pillars of functions, one common question arises:

### Are arguments passed by copy or by reference?

The best answer is neither. In Python you cannot control it in an *ad-hoc* manner as in `C`

.

Arguments in functions work just as we described in the in the names and values chapter. Whenever you call a function, the parameter becomes another name for the same value. If a mutable object like a list is passed, the caller will see any changes the callee makes to it.

Hence, instead of calling a function by copy or by reference, the accurate way to describe it is *by object-reference*.

As an illustration, find below 3 different pieces of code that intend to append a value to a list twice.

#### Option 1

```
def append_twice(a_list, val):
a_list.append(val)
a_list.append(val)
l = [1, 2]
append_twice(l, 'a')
print(l)
```

As a sidenote, if there is no return statement specified, automatically `None`

is returned.

#### Option 2

```
def append_twice(a_list, val):
a_list = a_list + [val, val]
l = [1, 2]
append_twice(l, 'a')
print(l)
```

#### Option 3

```
def append_twice(a_list, val):
a_list = a_list + [val, val]
return a_list
l = [1, 2]
l = append_twice(l, 'a')
print(l)
```

## Default arguments

In general, we want to compute the numbers of the Fibonacci sequence $F_n = F_{n-1} + F_{n-2}$ with initial conditions $F_0 = 0$ and $F_1 = 1$ by default. However, we can allow some flexibility by letting $F_0 = a$ and $F_1 = b$. This idea translates directly to Python functions:

```
def fibonacci(n, a=0, b=1):
"""Returns the n-th Fibonacci number."""
for i in range(n):
a, b = b, a+b
return a
```

With a single argument, the result of the function call is identical to before,

```
>>> fibonacci(3)
2
```

But we can now explore more sequences. The arguments can also be specified by its name and in this case the order does not matter.

```
>>> fibonacci(3, 1, 4)
9
>>> fibonacci(b=4, a=1, n=4)
9
```

More advanced capabilities of functions are detailed in the Powerful functions section.

**Lliçons.jutge.org**

Víctor Adell

Universitat Politècnica de Catalunya, 2023

Prohibit copiar. Tots els drets reservats.

*No copy allowed. All rights reserved.*