manyspikes

Functions

We have now learned quite a bit about Python—enough to start writing pretty complicate programs anyway.

As we start writing more complex code, we will soon find out that reusing code is quite important. As a general principle, duplicating code should always be avoided as this makes programs harder to understand and maintain.

Functions are one of the tools you can use to avoid code duplication. In a nutshell, they let you split your code into different components that perform different tasks. Each component optinally takes in some inputs and always produces an output.

Here is how to define a function that calculates the mean of a list of data points:

def mean(data): """Calculates the mean of a collection of values. Args: data: A collection of numeric values Returns: The mean of data. """ return sum(data)/len(data)

Let's first look at the anatomy of a funciton definition. It starts with the keyword def, and is followed by the function name and a comma-separated list of arguments between parenthesis. Finally, a colon signals that we are about to define the function body (the block of code that actually implements the logic we are interested in).

The body of the function starts in the line below with one level of indentation relative to the definition. The first line of the body is usually a multiline string (referred to as a docstring) which serves the purpose of documenting what the function does, what arguments it epects and what outputs it produces.

The remaining part of the body deals with whatever logic needs to be performed within the function and ends with a return statement, which defines the output of the function. In this case, we are returning the result of dividing the sum of data by the length of data.

If you do not write a return statement, a Python function will default to returning the value None.

Now that we have defined the function, let's look at how to use it:

data = [1, 2, 3] mean_data = mean(data)

The main advantage of using a function is that you write the logic for calculating the mean in a single place, and you can then use it in multiple places. If for some reason you need to change the implementation for calculating the mean, you only need to do it in one place.

Multiple and default arguments

As we mentioned above, functions can take more than one argument:

def poly2(x, a, b, c): """Calculate the value of a quadratic polynomial with parameters a, b, c.""" return a * x**2 + b * x + c

The function above takes as input 4 arguments. To call it, we would write:

poly2(3, 2, 1, 0)

Sometimes you might want an argument to be optional, which means that you can choose to provide it but, if you don't, Python uses a default value for it instead. For instance:

def poly2(x, a, b, c=0): """Calculate the value of a quadratic polynomial with parameters a, b, c.""" return a * x**2 + b * x + c

By writing c=0 you are defining a default value for the variable c. If you provide a value for c in the function arguments, the default value will be overriden; if you don't, Python will use the default value. In the following call

poly2(3, 2, 1)

Python will see that you have not provided a value for c, so it will use its default value instead. However, if you had not defined a default value for c with c=0, Python would raise an exception stating that the function call was not provided with enough arguments.

NOTE: Never define a optional argument with a default value that is mutable (e.g. a list or a dictionary). For reasons we will understand in more advanced modules, this can lead to unexpected behaviour.

Returning multiple values

Similarly to specifying multiple arguments, you can specify multiple return values by separating the values with commas, e.g.:

def generate_data(): return 4, 2, 3, 5, 1, 4, 3, 2, 2, 1

In reality, Python returns a single value, which is a tuple containing the values specified followint the return statement. For instance, the snippet

data = generate_data() print(type(data))

would print <class 'tuple'>. The value of data is simply a tuple. As we will see later, tuples can be unpacked, which makes it possible to write the following:

def generate_dimensions(): width = 10 height = 20 return width, height rect_width, rect_height = generate_dimensions()

What is happening above is that the values of the tuple that is returned by generate_dimenions are being assigned into the variables rect_width and rect_height, according to the order in which they were defined following the return keyword.