4.1 Simple functions

4.2 The call stack

4.2.1 Call procedure

4.2.2`yield`

4.2.3 coroutines

4.3 Higher-order

4.4 Decorators

4.5 Recursion

4.2 The call stack

4.2.1 Call procedure

4.2.2

4.2.3 coroutines

4.3 Higher-order

4.4 Decorators

4.5 Recursion

A function is defined and called using the terms:

- Create a function with signature
`function_name([formal_parameters])` - Set the body of function (in heap
^{2}) - Bind
`function_name`to the function in current frame

1. Names of `builtins` functions.

Example 4.1

💡How to fix it?

2. Modify values out of the function scope

💡Why do we need Python functions?

Python functions either (1) returns some value to the caller, or (2) have some side effects.

```
/* side effect */
void swap(int *x, int *y)
{
int *tmp;
tmp = x;
x = y;
y = tmp;
return;
}
/* return value */
int* createArray()
{
int res[10];
return &(res[0]);
}
```

Example 4.2 (a common gotcha)

3. Chained call of function

Example 4.3

- Add a local frame, forming a new environment
- Bind the function's formal parameters to its arguments in that frame
- Execute the body of the function in that new environment

🎨 **Time to draw!**

To visualize: use `pythontutor`!

Generators are also functions. The object is generator-iterator. The generator function differs from normal functions in that the frame is SUSPENDED after executing `yield`. It is resumed with certain behaviors.

Example 4.4

The `yield` statement can do more than sending out results to generators.

Example 4.5

code source: Fluent Python
In Python, everything is an object. So when we call `f(x=1)`, we pass an `int` object to `x`. Similarly, functions in Python are also objects. We can also pass functions as parameters to a function and receive another function as the output. Therefore, we abstract the computational processes. For example, consider implementing 3 machine learning algorithms to analyze data, we can store the detailed algorithm code in one file but in our `main.py`, we can use a common function to call the algorithms.

Example 4.6 (function as arguments, first-class)

Example 4.7 (function as return value)

We use decorators to simplify higher-order functions. One important usage of decorators is to "add" extra behaviors of other functions. Let's go back to the `fibonacci` function we defined. The reason it takes long time to get returns for `fibonacci(35)` is the recursive computation of `fibonacci(1)` through `fibonacci(34)`. Suppose we can save the already-computed results instead of call back itself. The computation time can be greatly shortened. The question is, how can we implement such a method?

Example 4.8

Remember: decorator is applied to function at definition, not at runtime. So retroactively modify the definition of the decorator function will not change the already defined-decorated function.

When we want to access the attributes of the functions, a useful decorator is `@functools.wraps`.

Example 4.9

Remember: we need to "prime" a coroutine so that we can `send` values to it? People thinking of it as tedious wrap the generator function in a decorator.

Example 4.10

Learn more about decorators here.

We will talk about a more complicated example here.

Thinking about adding parameters to the decorator? Check this out.

When a function refers to itself inside the body function ...

Example 4.11 (Recursion 1)

Example 4.12 (Recursion 1)

Recursion is very useful. For example, we can implement the auto differentiation method later.