Defining functions¶

This notebook covers roughly the same ground as this video.

We can define a function called double(x) using the keyword def. Note that the line starting with def must end with a colon, and the function definition itself must be indented, usually by four spaces. To specify the actual result of the function, we use the keyword return. (If there is no return statement, then the return value will be the special object called None.)

We define double(x) to be x * 2. If x is a number, then x * 2 is twice as big. If x is a string or a list, then x * 2 consists of two copies of x joined together. If x is a set then 2 * x is undefined, so double(x) will generate an error. The same applies for most other types of object.

In [ ]:
def double(x):
    return x * 2

print(double(1111))
print(double('abcd'))
print(double([1, 2, 3]))
# print(double({1, 2, 3})) # Generates an error: we cannot double a set
In [ ]:
def square(x):
    return x ** 2

print(square(1111))
# print(square('abcd')) # Generates an error: we cannot square a string

All but the most trivial functions should be documented with a docstring, as shown below. For any serious project you should follow the PEP 257 guidelines for the structure of docstrings.

This example also illustrates the idea of default arguments. The function emphasise() has three arguments, called s, excitement and doubt. It expects s to be a string, and excitement and doubt to be nonnegative integers. It returns the string s with some exclamation marks and question marks added on the end. The number of exclamation marks is excitement, and the number of question marks is doubt. Because the function definition says doubt = 0, we can omit the third argument and then doubt will take the default value of zero, so we will not get any question marks. Because the function definition says excitement = 1, we can omit the second argument and then excitement will take the default value of one, so we will get a single exclamation mark.

In [ ]:
def emphasise(s, excitement = 1, doubt = 0):
    """Add emphasis to a string by adding punctuation.
    
    s: the string to emphasise
    excitement: the number of exclamation marks to add
    doubt: the number of question marks to add
    """
    return s + '!' * excitement + '?' * doubt

print(emphasise('Wow, that was fun')) # Uses the default values for excitement and doubt
print(emphasise('Hi',7)) # Uses the value 7 for excitement and the default value of zero for doubt
print(emphasise('What on earth was that', 2, 5)) # Uses the values 2 and 5 for excitement and doubt
# When we specify the names of the arguments, we can change the order
print(emphasise('What on earth was that', doubt = 2, excitement = 5)) 

Typing ?emphasise or help(emphasise) will print the docstring together with some other basic information about the function. Alternatively, you can go to any cell that uses the emphasise() function and hover with your mouse over the word emphasise; then a box will pop up showing the same information.

In [ ]:
help(emphasise)

You can also use the same mechanisms to get information about built in Python functions, such as str.join.

In [ ]:
?str.join

As another example, we introduce a function to calculate a list of Fibonacci numbers $F_n$. These are defined as follows: we start with $F_0=0$ and $F_1=1$, and then for $n\geq 2$ we define $F_n=F_{n-1}+F_{n-2}$. For example, we have \begin{align*} F_2 &= F_1+F_0 = 0+1 = 1 \\ F_3 &= F_2+F_1 = 1+1 = 2 \\ F_4 &= F_3+F_2 = 1+2 = 3 \\ F_5 &= F_4+F_3 = 3+2 = 5. \end{align*}

The function fibonacci(n) below expects $n$ to be an integer which is at least two, and it returns the list $(F_0,F_1,F_2,\dotsc,F_{n-1})$ consisting of the first $n$ Fibonacci numbers. The method is as follows: we start with the list (0,1), then we repeatedly extend the list by adding together the last two entries and appending the sum as a new entry.

We have also used this example to introduce a mechanism for handling errors. The definition of $F_n$ is only meaningful if $n$ a nonnegative integer, so it is sensible to generate an error message if that is not the case. We do this using the keyword raise. This must be followed by an object encapsulating information about the error. Various different error objects are appropriate in different situations. In particular, TypeError should be used when a variable has an inappropriate type, and ValueError when a variable has the right type but an inappropriate value. (It would also work to use RuntimeError for everything, but that is not recommended in a well-structured project.) We have chosen to raise an error if $n\lt 2$. As a small exercise, you could modify the function to do the right thing when $n=0$ or $n=1$, and only raise an error if $n\lt 0$.

Raising errors is only half of the story. Later you will need to learn about try ... except blocks, which enable you to handle errors generated by other parts of your code. You can find information about all this at docs.python.org.

In [1]:
def fibonacci(n):
    """Return the list of the first n Fibonacci numbers."""
    if not(isinstance(n, int)):
        raise TypeError('n must be a integer')
    if n < 2:
        raise ValueError('n must be a integer >= 2')
    fib = [0, 1]
    for i in range(2, n):
        fib.append(fib[-1] + fib[-2])
    return fib

print(fibonacci(10))
# print(fibonacci(1.5)) # Generates an error: we cannot compute the Fibonacci sequence for a non-integer value
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

It is a standard fact about the Fibonacci numbers $f_n$ that $f_n/f_{n-1}$ converges to the Golden Ratio $\tau=(1+\sqrt{5})/2$ as $n\to\infty$. We will check this next.

In [ ]:
tau = (1 + 5 ** 0.5) / 2   # The true golden ratio
l  = fibonacci(100)      
tau_approx = l[-1] / l[-2] # The approximate golden ratio f_{99}/f_{98}
print(tau, tau_approx, tau - tau_approx)
In [ ]: