Lecture 2.3

Five Function Use Cases

Python and Big Data in Economics

Guoliang Ma
The Chow Institute, 2026

What you will learn

Five use cases of Python functions.

The Eighth Python Enhancement Proposal.

2.4 Use case I: decorator

AoL 3 (M)

Before we start decorators, let’s review what we’ve learned.

In-class exercise 1

Write a function to compute the average of t1.

Write a function to compute the standard deviation of t1.

Write a function to compute the skewness of t1.

t1 = (1, 2, 3, 4, 5)

2.4 Use case I: decorator

AoL 3 (M)

If you encountered an error when we tried to pass t2 to our function. The right thing to do is to make sure there is no invalid values in the arguments passed to our function. Here we introduce the assert statement.

assert is a claim, we assert an expression (what is an expression?). If the value of the expression is True, nothing happens and the program proceeds. But if the value is False the assertion fails, and an error occurs.

In-class exercise 2

Apply your average function to t2.

t2 = (1, 2, 3, None, 5)

2.4 Use case I: decorator

AoL 3 (M)

Examples of assert statements

In-class exercise 3

1. Use assert to improve your functions.

assert None in [1, 2, 3], "All values look good!"

assert None not in [1, 2, 3], "Contains None!"

2.4 Use case I: decorator

Although assert is handy, if we have written so many functions, it is hard for us to modify all of them. In other cases, if someone else defines the function for us, we cannot modify the function ourselves.

So when we want to extend the functionality of a function, what we can do is to define a higher-order function, and use the function we want to modify as its argument. Do whatever we want, and then send out the new function.

2.4 Use case I: decorator

AoL 2 (H)

Syntax template

# to define a decorator as a higher order function
def decor(func):

    def wrapper(*args, **kwargs):
        ...
        return func(*args, **kwargs)
        ...

    return wrapper

# to decorate a function
@decor
def func():
    pass

2.4 Use case I: decorator

AoL 3 (M)

In-class exercise 4

Write decorators to:

Print a line “<function name> function is being called." before the execution and print a line after the execution. "finish calling <function name> function.

Record return values of previous function calls. Every time a function is called, record the return value so next time the function is called, we don't have to wait the function to run.

In[]: print(1)
Out[]: print function is being called.
       finish calling print function.

2.4 Use case II: recursion

A function is recursive when it calls itself, directly or indirectly. Recursion does not require passing the function as an argument.

We’ll rewrite two functions we have already written and introduce three more examples:

Revisit Newton’s method

Revisit the sums function

Finding square root

Fibonacci sequence

Factorial function

2.4 Use case II: recursion (Newton’s method)

Suppose we want to find the root of 𝑓(𝑥)=𝑥2−3. Start from 0.5.

By Newton’s method,

𝑓(𝑥)=2𝑥

𝑥next=𝑥current𝑓(𝑥)𝑓(𝑥)=𝑥current𝑥2−32𝑥.

def newton(x):
    return x-(x**2-3)/(2*x)


x = 0.5
for _ in range(10):
    x = newton(x)
    print(x)

2.4 Use case II: recursion (Newton’s method)

Pay attention to the relation

Is it the same as

for _ in range(10):
    x = newton(x)
newton(newton(newton(newton(newton(newton(newton(newton(newton(newton(0.5))))))))))

2.4 Use case II: recursion (Newton’s method)

We can simplify the manual recursive calls

This unfinished starting point recurses without making progress. Add an update and a stopping condition before running it.

Summary:

A boundary condition is used to …

A recursion body is used to …

The difference between recursion and loops include …

def newton_recursion(x):
    return newton_recursion(x)  # unfinished: no update or stopping condition

2.4 Use case II: recursion (sum)

AoL 2 (H)

Based on what we learned from the previous example, can you see how the sum_recursion function works?

def sum_recursion(x):
    if len(x) == 0:
        return 0
    else:
        return x[0] + sum_recursion(x[1:])

2.4 Use case II: recursion (square root)

AoL 2 (H)

We have relied on the Newton’s method to find the square root of a number. There is another method.

Mathematically, if we want to find the root of 𝑥, the root must satisfy

𝑟=𝑥𝑟

If the relation is satisfied, then we will see

2𝑟=𝑟+𝑥𝑟

Or,

𝑟=0.5×(𝑟+𝑥𝑟)

2.4 Use case II: recursion (square root)

AoL 3 (M)

𝑟=0.5×(𝑟+𝑥𝑟) is a recursive relation

Why? If we add superscripts you’ll see

𝑟next=0.5×(𝑟current+𝑥𝑟current)

In-class exercise 5

Write a root_recursion function to find the square root of a number.

Hint: Start from finding the square root of 7 and then generalize your function.

2.4 Use case II: recursion (square root)

AoL 3 (M)

Recursive relation is fundamental in guiding the implementation of a recursion function. You’re (or should be) extremely familiar with it

In-class exercise 6

请推导斐波那契数列的通项公式。它的递推公式为:

𝑎𝑛+2=𝑎𝑛+1+𝑎𝑛

Please implement a Python function to compute the values of the Fibonacci sequence.

Hint: you can choose either approach.

2.4 Use case II: recursion (square root)

AoL 3 (M)

As a last exercise, we work on an easier one.

In-class exercise 7

Please write a function to compute the factorial of some number.

2.4 Use case III: map, filter, and reduce

AoL 2 (H)

Let’s start from the familiar max function. You already know the output of the max function:

If you try max(“hello”, “world”, “Python”), you’ll see Python can also compute the maximum. But what does it mean?

max((1, 2, 3))
max(1, 2, 3, 4)

2.4 Use case III: map, filter, and reduce

If we’re not satisfied with the default behavior, we can change it by defining a helper function that

takes the elements for comparison as parameters

returns the criterion by which we want to sort

def helper(s):
    return len(s)


max("hello", "world", "Python", key=helper)

2.4 Use case III: map, filter, and reduce

Is it unnecessary to define the helper function every time we want to find the maximum of some elements according to some standards. We can abandon the name of the function and just use it.

This is known as anonymous functions

The syntax is …

max("hello", "world", "Python", key=lambda x: len(x))

2.4 Use case III: map, filter, and reduce

Now let’s turn to three useful reducing functions.

The map function applies a function to every element of a sequence.

The filter function selects the elements according to a standard. Similar to the helper function in the map function. We need to set the standard as a function. But this time, the return value of the helper function has to be Boolean.

map(len, ("hello", "world", "Python"))
def long_string(x):
    return len(x) > 5


filter(long_string, ("hello", "world", "Python"))

2.4 Use case III: map, filter, and reduce

AoL 3 (M)

In-class exercise 8

Replace the helper function with an anonymous function.

2.4 Use case III: map, filter, and reduce

The last technique is the reduce function. Unlike the other two, we need to import it from the functools module.

The reduce function requires a helper function. The helper function for reduce must

take two parameters

return an accumulator that can be combined with the next item

In-class exercise 9

Search the web and explain what functional programming means.

from functools import reduce

reduce(lambda x, y: x + " " + y, ("hello", "world", "Python"))

2.4 Use case IV: generators

AoL 3 (M)

We have learned iterators (what is an iterator?). Generators are a special way to create iterators. There are several ways we can make generators.

We can create our own generators in two ways:

through generator expression and

through a generator function.

In-class exercise 10

List examples of functions that output generators.

2.4 Use case IV: generators (expression)

AoL 3 (M)

We learned the idea of comprehensions in Chapter 1. The parentheses enclosed comprehensions were not "tuple comprehensions." They are generators.

In-class exercise 11

Make a generator to help compute squares (1, 4, 9, 16, ...). What is necessary for you to define a generator like this?

type((x for x in range(5)))

2.4 Use case IV: generators (function)

Generator functions are almost the same as regular functions, except that we use the word yield. Wherever in a function there is an yield, the function becomes a generator function.

Yield means to give up right to someone else. The yield signs we see on road tell us to give up road right to others.

Five Function Use Cases: original illustration, slide 26.

2.4 Use case IV: generators (function)

In a computer program, when a generator function sees yield, it does the same thing. The generator function is suspended and other functions can use the computing resources. When we resume the generator with next() or iteration, it regains the computing resources until it sees yield next time.

2.4 Use case IV: generators (function)

AoL 2 (H)

Example 2.4.1 generator function

import time

def g():
    print(f'g() sleeping: gi_state: {g1.gi_running}')
    time.sleep(3)
    print(f'g() sees yield: gi_state: {g1.gi_running}')
    yield 1


g1 = g()
next(g1)
print("after, gi_state:", g1.gi_running)

2.4 Use case IV: generators (function)

Now we see how generator function works, and we can define a generator with it. Suppose we want to compute the squares of integers from 1 to infinity. This is impossible if we are using a list (why?). But with a generator, we don't need to store all squares in computer. Just compute them on-the-fly.

def squares():
    i = 1
    while True:
        yield i ** 2
        i += 1

2.4 Use case V: Error message

AoL 2 (H)

We introduce error message reading and handling here.

To handle an error, we rely on the try-except control flow.

The following code shows you how to print a hand-written number:

with open("../data/numbers/number1.csv", "r") as f:
    header = f.readline()
    data = f.readline()


import matplotlib.pyplot as plt
import numpy as np

pixels = np.array(data.split(","), dtype='uint8').reshape((28, 28))
plt.imshow(pixels)
plt.imsave("./mnist1.png", pixels)

2.4 Use case V: Error message

AoL 3 (M)

You can see that the flow is interrupted because of an error. But all other pictures are good. Checking all data before we plot and save the pictures is a daunting task. So we need to rely on the try-except control flow. The syntax is as follows:

In-class exercise 12

Plot and save all numbers in the numbers folder.

2.4 Use case V: Error message

try:
    ...
except KeyError as e:
    ...
else:
    ...
finally:
    ...

2.5 PEP 8

The Python Enhancement Proposals (PEPs) are a set of documents aiming at improving the Python programming language.

PEP 8 is a style guide for coding in Python.

It is not required.

It is recommended.

References and read more

AoL 5 (H)