# Function Parameters in Python

This post assumes familiarity with the concept of a function, and how they work. It's not necessary to know exactly how they work in Python, but if you don't understand functions, you should go and read up on them first.

### Arguments

Let's take a look at a very simple python function, one you've almost certainly seen in almost every single tutorial ever.

```
def add(a, b):
return a + b
```

This takes 2 numbers as input (`a`

and `b`

), adds them together and returns the result. It's predictable, you can see what you need to pass in, and are pretty sure you could calculate the result in advance.

`a`

and `b`

in this function are called * arguments*.

Specifying them in this way means they are both mandatory. Calling `add`

with only one parameter will give an error.

```
TypeError: add() missing 1 required positional argument: 'b'
```

Calling `add`

with too many arguments will give a different type of error.

```
TypeError: add() takes 2 positional arguments but 3 were given
```

So what if we wanted to add either 2 or 3 numbers? Would we need to define 2 functions?

```
def add2num(a, b): ...
def add3num(a, b, c): ...
```

I'm sure you can guess already that the answer is "of course not". There are actually 2 ways we could solve this problem.

### Optional arguments

We can add a third argument in as optional and set a default value that will not affect the final result.

```
In [1]: def add(a, b, c=0):
...: return a + b + c
In [2]: add(1, 2)
Out[2]: 3
In [3]: add(1, 2, 3)
Out[4]: 6
```

When we pass in only 2 values(`a`

and `b`

), the function still receives 3 values (`a`

, `b`

and `c`

), but uses the default value of `0`

for `c`

that we specify in the function definition, which of course is the identidy property for addition.

An

identity propertyis a value which, when applied to a mathematical operation, leaves the original value the same. For addition, the identity is`0`

becuase`a + 0 = a`

. For multiplication is is`1`

becuase`a * 1 = a`

.

`a`

and `b`

are still mandatory values, so this function requires either 2 or 3 values to be passed to it, or it will throw one of the errors above.

The next way of solving the problem of an optional 3rd parameter is a little more flexible.

#### Using **args*

It is also possible to specify an indefinite number of arguments to a function.

```
In [1]: def add(*args):
...: return sum(args)
In [2]: add(1)
Out[2]: 1
In [3]: add(1, 2)
Out[3]: 3
In [4]: add(1, 2, 3)
Out[4]: 6
In [5]: add(1, 2, 3, 4, 5)
Out[5]: 15
```

Ignore for the moment the fact that all this function really does is effectivly add an alias called `add`

to the built-in Python `sum`

- we're interested in how the arguments work, not the contents of the function itself. The function could equally have run a `for`

loop to add the numbers up

Adding a print statement into the function, we can see that the value of `args`

is actually a tuple, which is why we can call `sum`

on it.

```
In [6]: def add(*args):
...: print(args)
...: return sum(args)
In [7]: add(1, 2, 3, 4)
(1, 2, 3, 4)
Out[7]: 10
```

### Keyword arguments

We've seen a keyword argument already, when we set an optional argument for `c=0`

above. In this case, the keyword was `c`

and the default value was `0`

.

Keyword arguments need to come after all the "regular" arguments, so in the example above attempting to define `b`

as the optional parameter would not have worked.

```
In [1]: def add(a, b=0, c):
...: return a + b + c
File "<ipython-input-25-aa8377392da9>", line 1
def add(a, b=0, c):
^
SyntaxError: non-default argument follows default argument
```

### Positional-only arguments

When we call our orignal `add(a, b)`

function it didn't really matter whih variable was `a`

and which was `b`

, since addition is commutative

In maths, a

commutativeoperation is one where the order of the elements does not affect the result. Addition is commutative because`a + b = b + a`

, but subtraction is not because`a - b != b - a`

(at least, not usually).

Returning to the original function, we are allowed to specify the variable names when calling the function, rather than relying on the order.

```
In [1]: def add(a, b):
...: return a + b
...:
In [2]: add(1, 3)
Out[2]: 4
In [3]: add(b=3, a=1)
Out[3]: 4
```

So as you can see, `add(1, 3)`

is exactly the same as `add(b=3, a=1)`

, even down to the fact that `a==1`

and `b==3`

in both cases - in the first case because the ordering dictates it, and in the second because we have overridden the ordering to specify the variable names and their values.

Let's extend the function a little so that the order becomes a little more important.

```
In [4]: def add(a, b, opr='add'):
...: if opr == 'sub':
...: return a - b
...: return a + b
...:
In [5]: add(1, 3)
Out[5]: 4
In [6]: add(1, 3, 'sub')
Out[6]: -2
```

Not specifying the `opr`

parameter defaults it to `add`

, but we can override the operation by setting it to `sub`

instead.

Note that once you have set an optional parameter like this in the form `key=val`

, you cannot follow it by a positional parameter.

```
In [7]: add(1, 3, 'sub', False, True)
Out[7]: -2
In [8]: add(1, 3, 'sub', do_even_more=False, do_more=True)
Out[8]: -2
```

When calling the function, swapping the order of `a`

, `b`

and `opr`

would cause issues here if you didn't specify the variable names.

```
In [9]: add(1, 'sub', 3)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-38-1434c44c86cc> in <module>
----> 1 add(1, 'sub', 3)
<ipython-input-35-4f508039cc33> in add(a, b, opr)
2 if opr == 'sub':
3 return a - b
----> 4 return a + b
5
TypeError: unsupported operand type(s) for +: 'int' and 'str'
```

Not entirely surpising, since `sub`

is a string and so we can't add it to a number! We could do this ...

```
In [10]: add(1, opr='sub', b=3)
Out[10]: -2
```

But frankly it's all starting to look a little confusing now. Wouldn't it be simpler if we just said all these parameters were positional only? It would certainly make documenting the function simpler, and avoid confusion for anybody wanting to call it.

Remember the Zen of Python says

There should be one-- and preferably only one --obvious way to do it.

Python allows us to specify *positional only* parameters by simply putting a slash (`/`

) after the last positional only parameter.

```
In [11]: def add(a, b, opr='add', /):
...: if opr == 'sub':
...: return a - b
...: return a + b
In [12]: add(1, 3, opr='sub')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-10-aec68a30c86f> in <module>
----> 1 add(1, 3, opr='sub')
TypeError: add() got some positional-only arguments passed as keyword arguments: 'opr'
```

Here, you can see that just specifying the `opr=`

in the call raises an error, even though the parameters are all specified in the correct order. The extra slash enforces this.

The slash doesn't need to be the end of the parameter list, but anything that comes after it can be set using `key=val`

as well as position.

```
def add(a, b, opr='add', /, do_more=False, do_even_more=True):
if do_more:
pass # do other stuff here
if do_even_more:
pass
if opr == 'sub':
return a - b
return a + b
```

To set `do_more`

and `do_even_more`

we could set them positionally - in order - as the final arguments, or specify the parameter names.

```
In [13]: add(1, 3, 'sub', False, True)
Out[13]: -2
In [14]: add(1, 3, 'sub', do_even_more=False, do_more=True)
Out[14]: -2
```

Both of these calls are exactly the same, but because of the slash, both require the first 3 paramters to be set based on their position only.

### Keyword-only arguments

In much the same way as using the slash to set the end of the positional-only argument list, we can also use an asterix (`*`

) to set the beginning of the keyword-only parameter list. This can be combined with the slash value to effectively split the parameter list into 3 parts:

```
def function_name(<positional only>, /, <positional or keyword>, *, <keyword only>)
eg.
def function_name(a, b, c, /, d=0, e=0, *, f=0):
```

In this example, `a`

, `b`

, and `c`

, must be positional, `d`

and `e`

could be positional or specifid using their names, and `f`

can only be specifid by `f=value`

.

Let's return to the earlier function and add one more parameter.

```
def add(a, b, opr='add', /, do_more=False, do_even_more=True, *, do_yet_more=False):
if do_more:
pass # do other stuff here
if do_even_more or do_yet_more:
pass
if opr == 'sub':
return a - b
return a + b
```

Here, `a`

, `b`

and `opr`

are positional only, `do_more`

and `do_even_more`

could be specified by their position or name, and `do_yet_more`

must be set by name.

The following calls would all be valid:

```
add(1, 2, 'sub', True, do_even_more=False)
add(1, 2, 'sub', True, do_even_more=False, do_yet_more=True)
add(1, 2)
```

This call would raise an error:

```
add(1, 2, 'sub', True, False, False)
```

#### Watch out for ...

If you omitted `opr`

(which is OK becuase it has a default value of `add`

) and then try to set `do_more`

by position, i.e `add(1, 2, True)`

, no error will be raised, but becuase you are setting the values positionally, `True`

will feed into `opr`

, which could have unintended consequences for your process.