\documentclass[../../book.tex]{subfiles}
\graphicspath{{\subfix{.}}}


\begin{document}

\chapter{Functions}
\label{ch:functions}
\setcounter{section}{-1}  % Start subsections from 0


In many ways, functions are what give programming its power. Functions allow us to efficiently re-use code, minimizing the lines of code we need to write. This makes us more efficient coders and less likely to make mistakes. 

Python has many built-in \cite{functions} that we have already been using throughout this book.  For instance, when we create a \lstinline{float} from an \lstinline{int} using \lstinline{int()}, we are using the function \lstinline{int()}. 

However, there will be multiple times that you want to do something that Python does not have a built in function for. As an example, Python does not have a built in function to normalize spectroscopic data---a process that is regularly used when comparing spectra. However, Python \textit{does} allow you to create your own function that will do this, and then call that function later on. Thus,  to better understand functions and realize their true power, it is important to understand how to create and use your own. 



\section{Creating and using functions}

In Python, creating a function is called 'defining' the function. A basic function definition looks like this:

\begin{lstlisting}
def add_numbers(parameters):
    # do some things you want to do
    return # can can follow the word "return" with a value or not.
\end{lstlisting}

We begin the definition with the \cite{keyword} \lstinline{def}, which tells the Python interpreter that what follows is the definition of a function. 

Immediately following \lstinline{def} we provide the name of the function.  This name must be all one word, and must follow standard naming rules (see Section \ref{Temporarily_storing_data_using_variables}). As with variables, it is best to give these functions descriptive names. Since functions do things, it can be helpful to include a verb in the name. 

Following the name, we have the parameters, held between parentheses. Parameters are pieces of data that the function needs (or can optionally use) to operate. You can have any number of parameters you want (including no parameters) and you separate them by commas. It is important to realize that there are a number of different types of parameters,\footnote{You might see these called `\cite{arguments}'.  Parameters are arguments are distinct, but closely related ideas.  A brief disambiguation is this: parameters are the names of variables that the function expects to receive, while arguments are the values passed to these parameters when the function is \cite{call}ed.} and we we will discuss each of them the next section.

The first line ends with a colon, and this tells the interpreter that the series of operations that the function will specify follows on the next line(s). This is also considered a code \cite{block}, and so the lines that follow the definition must be indented, with respect to the definition line. This block can contain any number of lines that have valid Python code, such as \textbf{control structures}. It could even include the definition of another function.

You end the function with a line that begins with the \cite{keyword} \ttcite{return}. The things that follow \lstinline{return} will be passed back when the function finishes running.  If you supply no values, then the function simply ends, without returning any values.  Or, more properly, it returns the value \lstinline{None}.\footnote{You can find this data type discussed in Chapter \ref{DataTypes}.}

Considering a very simple example can help us see how all this fits together. Functions are best used to gather together multi-step operations or to simplify the handling of operations that will require multiple inputs. One operation that  that requires multiple inputs and is not uncommon in chemistry is the conversion of mass to number (or counts) of things (i.e., moles). This involves knowing the mass, the molecular mass, and also using the constant Avogardo's number. In Python, we might generate a function that accomplishes this task as follows: 

\begin{lstlisting}
def mass_to_counts (mass, MW):
    count = mass/MW*6.022*10**23
    return count
\end{lstlisting}

If we run this code, we will have now defined a function that will take a mass and a molecular mass and convert the grams into number of atom or molecules. We say that \lstinline{mass} and \lstinline{MW} are parameters of the function.  When we want to use the function---which is termed `\cite{call}ing' the function, we simply give the name of the function and then supply values (arguments) that will be passed to the function's parameters. The function performs a series of operations, and then returns a value. For instance, imagine we wanted to know how many molecules were in 100g of water. 

\begin{pyin}
mass_to_number(100, 18.02)
\end{pyin}
\begin{pyout}
3.341842397336293e+24
\end{pyout}

Here we have given values of 100 for the mass, and 18.02 for the molecular weight. Importantly, we did not supply units, so we have to keep track of this.  Also, the output is not formatted in a super convenient way, but this could be adjusted as described in the technical chapter on text (Chapter \ref{Text}).

In the above case, we used numbers directly, but we could also supply variables to the function:\footnote{Again, even though we have variables that seem to have units in the name, Python does not know this.  Please be careful with keeping track of units. Here, it is done using comments, so that we can remind ourselves what we expect. }

\begin{lstlisting}
g = 100 # g
gmol = 18.02 # g/mol
mass_to_number(g, gmol)
\end{lstlisting}
\begin{pyout}
3.341842397336293e+24
\end{pyout}

Also, we can choose to assign the returned value to a variable by using the assignment operator (=):

\begin{lstlisting}
g = 100
gmol = 18.02
molecules = mass_to_number(g, gmol)
\end{lstlisting}

In this case, the returned value is not automatically output. In order to display the results, we can print the value of \lstinline{molecules}.

\begin{pyin}
print(molecules)
\end{pyin}
\begin{pyout}
3.341842397336293e+24
\end{pyout}

This is the basic manner of using functions. However, there are also some advanced concepts on using functions, specifically with respect to the parameters, which are worth discussing. We discuss these next.



\section{Function parameters}
\setcounter{subsection}{-1}  % Start subsections from 0

In Python, there are 4 types of parameters that can be used in a function. 


\subsection{Positional parameters}

These are probably what most people first encounter, and the type demonstrated in our example above. The basic idea behind this is that Python assigns the values of the variables within the function based upon the position of the arguments being passed to the function.  The first argument is assigned to the first function parameter, and so on.  In our example above, Python `knew' that 100 goes with mass and 18.02 with MW because we gave the mass first and the MW value second. 

If we changed the order of things, then we would get a very different answer:

\begin{pyin}
    mass_to_number(18.02, 100)
\end{pyin}
\begin{pyout}
    1.0851644e+23
\end{pyout}

We get this answer because Python assigned 18.02 to the mass and 100 to the MW.  You can verify this by doing the calculation yourself and take this as another friendly reminder that Python has no idea what units the numbers have---so please be cautious of this.

Thus, when using positional arguments, it is important that you understand the order that values need to be supplied in, and then supply them in that order. 

What if we don't want to have to remember the order? Or maybe we have a list of values that are not in the same order as the function expects? Our next parameter type solves this problem. 

\subsection{Keyword parameters}
At their most basic, you can use parameters in the definition of the function as keywords to assign a value.\footnote{Read the subsection on parameter order to see when this will not work.} If we do this, then Python will assign the value to the parameter we want, even if we don't supply them in the correct order. 

\begin{pyin}
    mass_to_number (MW = 18.02, mass = 100)
\end{pyin}
\begin{pyout}
    3.341842397336293e+24
\end{pyout}

Now, even though we have supplied values in a different order from which they were defined, we get the desired outcome.  This is because we assigned values by keyword, which takes precedence over the ordering. 

So, why wouldn't we always use the parameters in this keyword style? While it is true that using keyword parameters means you don't have to know the order to specify them in, it has the disadvantage that you have to know the names of the parameters. Depending on the situation, one of these will be easier than the other, and so you will find yourself using both.  In this book, we find ourselves using positional more often than keyword. 

One thing to note is that we did not have to go out of our way in order to ensure these parameters were keyword parameters. Instead, being able to use them this way is an automatic result of the function definition. This may make it seem like positional parameters and keyword parameters are the same, but they are not quite. The difference comes in the places that positional and keyword parameters can occupy within the definition of a function. However, before we can truly appreciate this, we need to introduce the remaining two parameters. We will then return to the difference between positional and keyword parameters.


\subsection{Arbitrary positional parameters: \texttt{*args}}

These are positional parameters, but which can accept an arbitrary number of values.\footnote{As a reminder when a function is called, the values passed to the parameters are called `arguments.' This is why this parameter is called `*args.' It is referencing the \textit{use} (when they are called arguments) rather than the creation (when they are called parameters).} They are designated by placing an asterix in front of a parameter name.

Imagine that you had a specific compound (such a glucose) that you wanted to weigh out an arbitrary number of times.  You want to convert all these masses to the number of molecules per weighing and also get the mean value and standard deviation of these values. However, you don't know the number of times you are going to do the weighing. You could use the function above, just calling it multiple times, collecting the results, and the calculating the mean from them.  However, you could create a new function that more naturally handles this situation. 

\begin{lstlisting}
import numpy as np
def masses_to_stats (*masses):
    MW = 180.156 # g/mol for glucose
    N_A = 6.022*10**23
    numbers = []
    for mass in masses:
        numbers.append(mass / MW * N_A)
    numbers = np.array(numbers)
    return [np.mean(numbers), np.std(numbers)]
\end{lstlisting}

Using this function, we can supply any number of masses we want.

\begin{lstlisting}
    >>> masses_to_stats (95.2, 95.1, 95.2)
\end{lstlisting}
\begin{pyout}
    [3.181096753184277e+23, 1.9298848337330848e+20]
\end{pyout}

\begin{lstlisting}
    >>> masses_to_stats (44.8, 45.1, 45.2, 44.9)
\end{lstlisting}
\begin{pyout}
    [1.5041963631519348e+23, 6.102831696411533e+20]
\end{pyout}

And we will then have the means and standard deviations recorded for two different sets of values that had different numbers of values. 

What is happening is that Python is taking all of the values you gave the function, and putting them into \lstinline{tuple} (see Chapter \ref{DataTypes}) that the function assigns to the variable \lstinline{masses}. Thus, when you write code to use *args you need to realize you are using a \lstinline{tuple}.  

This also suggests that you could write code that gathers masses in a \lstinline{tuple} and pass them to a function with a normal positional argument, and this true. However, that requires more code \textit{outside} of the function. If we know that we want to have a sequential type that we will iterate through, then it is better practice to implement this in the function.  That way, if you need to call the function multiple times, you don't have to repeat the part of the code where you create a tuple first. Additionally, this can help you avoid unwanted scope problems associated with passing mutable lists to functions. This concept is discussed in more detail in the last section of this chapter. 


\subsection{Arbitrary keyword parameters: \texttt{**kwargs}}

If one also has the ability to supply an arbitrary number of keyword arguments using the \lstinline{**kwargs} parameter type. Doing so will build up a \cite{dictionary} of key:value pairs based on these keywords.\footnote{For more information on dictionaries, you can refer to Chapter \ref{DataTypes}.} 

Perhaps the reason you were weighing out multiple samples was that you are working in a group and you want to understand the standard deviation in measurements that each team member made. You could calculate this, while also keeping track of who did what. Perhaps you think it would be fun to know who's mass had the largest contribution to the standard deviation? This is, of course the same as who's mass is farthest from the mean

\begin{lstlisting}
import statistics as stats
def person_mass_to_molecule (**trial):  
    # make a list of values. 
    masses = []
    for person, mass in trial.items():
        masses.append(mass)
    # now get the mean value and 
    mean_mass = stats.mean(masses)
    diff = 0
    for person, mass in trial.items():
        if abs(mean-mass) > diff:
            diff = abs(mean-mass) # update the reference difference
            contributor = person
    return [mean_mass, stats.dev(numbers), contributor]
\end{lstlisting}

When calling this function, you can now give a series of key:value pairs that will be used in the function.

\begin{lstlisting}
    person_mass_to_stats(Ben = 14.3, Chris = 13.9, Stanley = 11.7, Shana = 12.3, Diana = 13.0)
\end{lstlisting}
\begin{pyout}
    
\end{pyout}
\lstinline{[13.040000000000001, 1.080740486888504, 'Stanley']}

Where the first number in the list is the mean value, the second is the standard deviation in values, and the third is the person who contributed most to the standard deviation. 



\subsection{Order of parameters: mixing and matching parameter types}

Above, we discussed the 4 different types of parameters that we can use in the definition of functions. We also showed their usage in isolation. We can also use multiple parameter types in a single function. 

However, when using more than a single type of parameter, there are rules for the order they are supplied in.  The basics are:

positional arguments $\rightarrow$ arbitrary positional arguments $\rightarrow$ keyword arguments $\rightarrow$ arbitrary keyword arguments. 

The fact that this ordering is enforced shows how we can make a clear distinction between positional and keyword arguments.  Specifically, if we have arbitrary positional arguments, then things that are to the right of them \textit{must} be keyword arguments of some type, while parameters to the left \textit{must} be positional. However, outside of this ordering, the distinction might be a bit academic. 

For instance, let us return to the example where we were calculating the amount of glucose over multiple weighing trials. When we did this, we `hard coded' the molecular mass into the function.  But if we used a keyword instead, we can supply the molecular mass when we call the function \textit{and} still have multiple measurements passed to an arbitrary positional parameters. 

\begin{lstlisting}
import statistics as stats
def masses_to_stats (*masses, MW):
    N_A = 6.022*10**23
    numbers = []
    for mass in masses:
        numbers.append(mass / MW * N_A)
    return [stats.mean(numbers), stats.stdev(numbers)]
\end{lstlisting}


Because we have added the MW parameter to the \textit{right} of the arbitrary positional parameter, then the MW is a keyword argument, and we can assign values to it as described above. 

Thus, if we had two measurements of water, we could have:

\begin{lstlisting}
    masses_to_stats(10.3, 11.1, MW = 18.02)
\end{lstlisting}
\begin{pyout}
    [3.575771365149834e+23, 1.890431536650561e+22]
\end{pyout}

Whereas if we had three measurements of \ce{Fe2O3}, we would have:

\begin{lstlisting}
    masses_to_stats(2.3, 2.1, 1.9, MW = 58.443)
\end{lstlisting}
\begin{pyout}
    [2.1638519583183615e+22, 2.0608113888746285e+21]
\end{pyout}



\subsection{Setting default values for parameters}

The final topic for definition of functions is to point out we can provide default values for parameters these defaults are used in the case that no value is provided when the function is called. However, because they are used when no value is provided, this also means that there must be a way to handle the value being present or absent. Both arbitrary positional parameters and arbitrary keyword parameters handle this case naturally: they can always accept between zero and an arbitrary number of parameters. However, this is not the case for the positional and keyword arguments. 

Instead, for both positional and keyword parameters, default values are specified using an assignment operator (=) when providing parameters in the function definition. However, assignment between argument and parameter depends on the number of parameters passed.  So to make this fault tolerant, once a default value has been specified, all other positional or keyword arguments to the right of that one \textit{must} also have default values provided.


\begin{lstlisting}
def myFunc (p1 , p2="True", *p3, p4 = "three", **p5):
\end{lstlisting}

In this definition, \lstinline{p1} is a positional parameter, but does not have a default value. \lstinline{p2} is also a positional argument (because it is left of the arbitrary positional parameter) but has a default value assigned. This means that all other positional or keyword parameters that follow also must have default values. \lstinline{*p3} is neither of these, since it is an arbitrary positional parameter, and so cannot have defaults provided in the definition line. \lstinline{p4} is a keyword argument (since it is to the right of the arbitrary positional argument) and since there was a positional argument with a default value to the left of this, it also must have a default value assigned. Finally, the parameter \lstinline{**p5} is a an arbitrary keyword argument and does not have defaults defined in the definition line. 

%But what if you wanted to have default values for arbitrary positional parameters and arbitrary keyword parameters? This can be handled after the definition line, within the function. 




\section{Scope}
\label{sec:T:Functions:scope}

A basic concept for using functions is this: if you define a variable within a function, you cannot access it outside the function---even if you have run the function already.

\begin{lstlisting}
def mass_to_number (mass, MW):
    number = mass/MW*6.022*10**23
    return number

mass_to_number(100, 18.02)

print(number)
\end{lstlisting}

If you run this code, you will get an error:

\begin{pyout}
    NameError: name 'number' is not defined
\end{pyout}

Looking at the code we ran, you can see that we had defined \lstinline{number}, and we even used it within the function with no problem (this was our first example in this chapter). But, this was done \textit{within} function.  The error happens when we try to access this function \textit{outside} the function.

On the other hand, if you have defined a variable outside of a function, you can access it inside the function.

\begin{lstlisting}
units_i = "grams"
units_f = "molecules"

def mass_to_number (mass, MW):
    number = mass/MW*6.022*10**23
    return f'measured {mass} {units_i}, which is {number} {units_f}'

mass_to_number(100, 18.02)
\end{lstlisting}

Running this code, we get the following output. 

\begin{pyout}
    'measured 100 grams, which is 3.341842397336293e+24 molecules'
\end{pyout}

Even though we defined the variables \lstinline{units_i} and \lstinline{units_f} outside the function, we were able to access them inside the function, which is how we returned the units in the string. 

That is scope in a nutshell. 

There are a few reasons to keep this in mind.  First, it does control what variables you can and cannot access, and this is important to remember. 

However, it also can be convenient, because if you have two functions that will never call one another, you can use the same variable names within them, and that can really aid you in keeping variable names simple. 

For example, when you are working with $x$ and $y$ data, you might be able to have multiple functions that use \lstinline{x} and \lstinline{y} as simple variable names. In the following example, we have two functions that work to either baseline correct data or normalize data, when that data is held in a Python list. 

\begin{lstlisting}
from plotly.subplots import make_subplots

x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
y = [1, 2, 3, 4, 5, 4, 2, -1, -2, -1, 0]


# baseline correct
def baseline_correct_data (y):
    low_value = min(y)
    corrected = []
    for v in y:
        corrected.append(v - low_value)
    return corrected

# normalize
def normalize_data (y):
    high_value = max(y)
    normalized = []
    for v in y:
        normalized.append(v/high_value)
    return normalized



norm_y = normalize_data(baseline_correct_data(copy.deepcopy(y)))

fig = make_subplots()
fig.add_scatter(x = x, y = y)
fig.add_scatter(x = x, y = norm_y)
fig.show("png")

\end{lstlisting}

Running this code will produce a default Plotly plot, with two traces. The first of the data that has not been altered, and the second of data that has been corrected so that the lowest point is 0 and then so that the highest point is 1. 
 



\section{Use \texttt{copy.deepcopy()} to pass mutable objects to functions}

\textbf{This applies to one of the most used data types in Python: the list.  So PLEASE read this section. }\\

Above, we used \lstinline{copy.deepcopy()} when passing our list to the function. Here, we explain why that is. To understand this discussion  you need to understand \cite{mutability}, as discussed in Chapter \ref{DataTypes} and it will be useful to also understand  \cite{memory}, which is covered in Chapter \ref{Computers}.  So you may need to read that as well. However, it is extremely important to understand why we used \lstinline{copy.deepcopy()}, because misunderstanding it leads to a large number of errors in Python code. 

The basic idea is this: when you pass values to a function, you are not actually giving the function that value, but the reference to that value in memory. 

The reason that this is important is that, if the object you are passing in mutable, then you can modify the values inside that object within the function, and these values will also change outside the function. 

Let us see an example with a common mutable type: lists. If we repeat the above example, but don't use \lstinline{copy.deepcopy()}, we will obtain two traces that are right on top of one another (it may look like a single trace). 

\begin{lstlisting}
norm_y = normalize_data(baseline_correct_data(y)

fig = make_subplots()
fig.add_scatter(x = x, y = y)
fig.add_scatter(x = x, y = norm_y)
fig.show("png")
\end{lstlisting}

This is because both lists are the same... or rather they are pointing to exactly the same values. Again, this is explained in Chapter \ref{DataTypes}. 


This behavior is different from if you had an immutable object.  The immutable analog to the list is the tuple, and if we repeat this with tuples we get the following:


\begin{lstlisting}
norm_y = normalize_data(baseline_correct_data(tuple(y)))

fig = make_subplots()
fig.add_scatter(x = x, y = y)
fig.add_scatter(x = x, y = norm_y)
fig.show("png")
\end{lstlisting}

Now, we again get two spectra that are different, the original and the baseline corrected and normalized spectra. This is because we converted the list to the tuple and then operated on this immutable object. 

The point here is that  you have to be very careful in how you handle mutable objects and functions, and so it would be a good idea to keep which data types are mutable and which are immutable. An explanation of what data types are mutable and not mutable is covered in Chapter \ref{DataTypes}. 

It may also seem that this behavior is somehow at odds with our discussion of scope above, however, they are slightly different.  In the discussion on scope, we said that if we create a new object within a function, we cannot access it outside the function. Here, we are saying that if we create a mutable object outside a function, we can still modify it within a function. 

Most of the time when people get in trouble with this behavior is when they pass a list to a function and forget that it will be modified, so they end up with a list that is different from what they think they have. Of course, if you do realize this, you could exploit this behavior to your advantage.  However, we think that, for the case of this book, best practice is to never pass a mutable object to a function. 

In the case of a list, we have two options: we can convert the \lstinline{list} to a \lstinline{tuple} (as was done above), or we can use the \lstinline{copy.deepcopy()} function to create a new list that is then passed to the function, as was done in our original example.

If you use the \lstinline{copy.deepcopy()}, we are technically passing a mutable object to the function, but it is a new object with no reference to it outside the function.  It is created as it is passed, so we will never have the opportunity to use it outside the function. 

It is the use of \lstinline{copy.deepcopy()} that we recommend using.  The reason is that it can handle mutable objects that hold other mutable objects. What \lstinline{copy.deepcopy()} does is to make a new copy of very item \textit{and sub-item} in the object you feed it.\footnote{The technical term is that it does this recursively.} It will do this for however many levels you have.  If you have a series of 100 nested lists, it will copy them all. 

For instance, imagine that we had a list that contained another list. If we turn the outer list into a tuple, the tuple still contains the mutable lists within it.  So, if we do something to alter those lists, \textbf{they will be altered outside the function.}



\section{Recursion}

There is nothing that prevents you from calling a function from within itself.  This is called `recursion,' and while it may sound strange, it can be a very powerful technique.  As the function executes, if it reaches another call to itself, it will pause the current progress through the function and start up another iteration from the beginning.  Like a loop, care must be taken to ensure that this does not happen infinitely by including some code that determines the appropriate time to no longer call the function again.  At that point, the last function call will complete, then the one before that, and the one before that, and so on until all of the nested function calls are complete.

Recursion is neat but is never necessary to solve a problem.  It can make solutions easier to understand and more readable.  Recursion particularly shines when an algorithm needs to iterate some number of times that is both not known at the time of coding and is difficult to express simply using conditional expressions.  An example of this is computing the isotopic abundances of molecules of arbitrary chemical formula---here, the number of each atom, and the number of different atoms, is unlikely to be known to the programmer, and a recursive algorithm is likely to be much simpler and less prone to error than a series of nested \lstinline{while} loops.

It is unlikely that most readers of this book will run into a recursion problem in the context of the material of the book, but it is a programming concept that is good to know about and worth reading more about if your interest is piqued.

\end{document}