Functions

Functions#

Functions are used to organize program flow, especially to allow us to easily do commonly needed tasks over and over again. We’ve already used a lot of functions, such as those that work on lists (append() and pop()) or strings (like replace()). Here we see how to write our own functions.

A function takes arguments, listed in the () and returns a value. Even if you don’t explicitly give a return value, one will be return (e.g., None).

Here’s a simple example of a function that takes a single argument, i

def my_fun(i):
    print(f"in the function, i = {i}")
    
my_fun(10)
my_fun(5)
in the function, i = 10
in the function, i = 5

Even though we didn’t explicitly return anything, a function always returns a value.

a = my_fun(0)
print(a)
in the function, i = 0
None

Functions are one place where scope comes into play. A function has its own namespace. If a variable is not defined in that function, then it will look to the namespace from where it was called to see if that variable exists there.

However, you should avoid this as much as possible (variables that persist across namespaces are called global variables).

We already saw one instance of namespaces when we imported from the math module.

Here’s a simple function that takes two numbers and returns their product.

def multiply(a, b):
    return a*b

c = multiply(3, 4)
print(c)
12

Exercise:

Write a simple function that takes a sentence (as a string) and returns an integer equal to the length of the longest word in the sentence. The len() function and the .split() methods will be useful here.

Keyword Arguments#

You can have optional arguments which provide defaults. Here’s a simple function that does some computation that takes a verbose keyword argument to print out some detail along the way.

def is_prime(n, verbose=False):
    """brute force check if n is prime, if verbose is True, then
    show the attempts along the way"""
    if n <= 1:
        # there are no negative primes, and 1 is not prime
        return False

    # start at 2, note that if n = 2, then this does nothing
    # and we will correctly return that 2 is prime
    for k in range(2, n):
        if verbose:
            print(f"checking {k:3d}: n % k = {n % k}")
        if n % k == 0:
            return False
            
    return True
is_prime(9, verbose=True)
checking   2: n % k = 1
checking   3: n % k = 0
False

Note

Our function also has a “docstring”, which allows us to ask for help

help(is_prime)
Help on function is_prime in module __main__:

is_prime(n, verbose=False)
    brute force check if n is prime, if verbose is True, then
    show the attempts along the way

Important

Python evaluates the optional arguments once—when the function is defined. This means that if you make the default an empty object, for instance, it will persist across all calls.

This is one of the most common errors for beginners.

Here’s an example of trying to initialize to an empty list:

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))
[1]
[1, 2]
[1, 2, 3]

Notice that each call does not create its own separate list. Instead a single empty list was created when the function was first processed, and this list persists in memory as the default value for the optional argument L.

If we want a unique list created each time (e.g., a separate place in memory), we instead initialize the argument’s value to None and then check its actual value and create an empty list in the function body itself if the default value was unchanged.

def fnew(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

print(fnew(1))
print(fnew(2))
print(fnew(3))
[1]
[2]
[3]
L = fnew(1)
print(fnew(2, L=L))
[1, 2]

Notice that the same None that we saw previously comes into play here.