---
jupyter:
  jupytext:
    split_at_heading: true
    text_representation:
      extension: .Rmd
      format_name: rmarkdown
      format_version: '1.1'
      jupytext_version: 1.2.4
  kernelspec:
    display_name: Python 3
    language: python
    name: python3
---

# Function arguments

So far we have used function arguments in a basic way; this is the way that is
familiar from mathematics:

```{python}
# Load the Numpy package, and rename to "np"
import numpy as np
```

```{python}
np.cos(0)
```

Here is another Numpy function, that you have already seen:

```{python}
np.arange(0, 10)
```

Remember, this is a sequence of integers, starting at 0 (the first argument),
up to *but not including* 10 (the second argument).

Now let us look at the help for the `np.arange` function.  As usual, we do this
by appending `?` to the function name, and pressing Enter in the notebook.

```{python}
# To see the help for np.arange, remove the # at the beginning
# of the next line, and execute this cell.
# np.arange?
```

You should see the help, preceded by a function signature something like this:

```
arange([start,] stop[, step,], dtype=None, *, like=None)
```

(Ignore the `*` in the signature for now. Also ignore the square brackets in
the signature, they do not change what arguments we can pass).  The signature
tells us function can accept up to five arguments. We have passed two. The
first sets the argument called `start` to be 0, and the second sets the
argument called `stop` to be 2.

To take another example, in this case we are asking for a sequence of numbers
starting at 1 up to, but not including 11. `start` is 1 and `stop` is 11.


```{python}
# All integers from 1 through 10.
np.arange(1, 11)
```

If we pass three arguments, we also set the `step` argument, to set the step
size between integers. The following asks for all integers from -1 up to, but
not including 20, in steps of 4.

```{python}
# All integers from -1 up to, but not including 20, in steps of 4.
np.arange(-1, 20, 4)
```

Notice that this is an *array*.

Now look again at the help.  Notice that the help gives each argument a *name*:
`start`, `stop`, `step`.  We can also use these names when we set these
arguments.  For example, the cell below does exactly the same thing as the cell
above.

```{python}
# All integers from -1 up to, but not including 20, in steps of 4.
np.arange(start=-1, stop=20, step=4)
```

When we call the function using the arguments with their names like this, the
named arguments are called *keyword* arguments.


Passing the arguments like this, using keywords, can be very useful, to make it
clearer what each argument means.   For example, it is a common pattern to call
a function with one or a few keyword arguments, like this:

```{python}
# Same as above.
np.arange(-1, 20, step=4)
```

Writing the call like the cell gives exactly the same result as the cell below,
but the cell above can be easier to follow, because the person reading the code
does not have to guess what the 4 means — they can see that it means the step
of the output array.

Another large benefit of keyword arguments is that it allows us to skip some
arguments we are not interested in.  For example, lets say we are happy with
the default `step` size of 1, but we do want to pass the `dtype` argument, that
sets the data type of the output array.

We could do that by passing the default step size, like this:

```{python}
# Float dtype, specifying step size.
np.arange(1, 11, 1, dtype=float)
```

Notice that the array values now have decimal points after them, indicating
that they are floating point values.

But we have already seen that the `step` size is a keyword argument, with a
*default* value of 1.  That means, if we don't specify the `step`, the assumed
value will be 1.   We can therefore specify `dtype` without having to specify
`step`, like this:

```{python}
# Same as above.  Notice, we didn't specify `step` this time.
np.arange(1, 11,  dtype=float)
```

To take another example, we have already seen the function `round`.  Inspect
the help for `round` with `round?` and Enter in a notebook cell.

`round` takes up to two arguments.  If we pass one argument, it is just the
value that `round` will round to the nearest integer:

```{python}
round(3.1415)
```

If we pass two arguments, the first argument is the value we will round, and
the second is the number of digits to round to, like this:

```{python}
round(3.1415, 2)
```

As you saw in the help, the second argument has the name `ndigits`, so we can
also write:

```{python}
round(3.1415, ndigits=2)
```

As before, this makes the code a little bit easier to read and understand,
because it is immediately clear from the name `ndigits` that the 2 means the
number of digits to round to.
