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

Functions

4.1 Simple functions

A function is defined and called using the terms:

Function CREATION procedure:1

  1. Create a function with signature function_name([formal_parameters])
  2. Set the body of function (in heap2)
  3. 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

        

4.2 How is a function called (stack frames and namespace)

1. Function CALL procedure:3

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

🎨 Time to draw!

To visualize: use pythontutor!

2. The yield statement

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

    

3. yield for coroutines

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

Example 4.5
code source: Fluent Python

    



4.3 Higher order functions

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)

    

4.4 Decorators

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.

4.5 Recursion

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.


1., 3. [Reference: John DeNero, CS61A, UCB]
2. Heap stores objects; stack stores references.

Back⏎