Positional-only arguments in Python
Tags: python, tutorial
Published: 2019-08-27 08:02:00

The ability to specify positional-only arguments using the `/` marker in function definitions is among the many new improvements to the Python language coming in the upcoming [3.8 release](https://deepsource.io/blog/python-3-8-whats-new/). This addition to syntax has performance benefits and enables better API design. Let's look at the motivation behind positional-only arguments and how to use it, with examples.

## Background

Keyword-only arguments have been available in Python with the `*` marker, and addition of `/` marker for positional-only arguments improves the language’s consistency. With positional-or-keyword parameters, the mix of calling conventions is not always desirable. Consider these examples:

* Some function parameters already have semantic meaning: `namedtuple(typenames, field_names, …)`
* The parameter name has no true external meaning: `arg1`, `arg2`, …, etc for `min()`

If the users start using a keyword argument, the library author cannot rename the parameter because it would be a breaking change. In case of `min()`, the name of the parameter provides no intrinsic value and forces the author to maintain its name forever since callers might pass arguments as a keywords. This problem is solved by positional-only arguments. In addition, the parsing and handling of positional-only arguments is faster.

## How to use positional-only arguments

To specify arguments as positional-only, a `/` marker should be added _after_ all those arguments in the function definition. Take this example:

```python
def pow(x, y, /, mod=None):
    r = x ** y
    if mod is not None:
        r %= mod
    return r
```

The following would apply:

* All parameters to the _left_ of `/` (in this case, `x` and `y`) can only be passed positionally.
* `mod` can be passed positionally or with a keyword.

```python
>>> pow(2, 10)  # valid
>>> pow(2, 10, 17)  # valid
>>> pow(2, 10, mod=17)  # valid
>>> pow(x=2, y=10)  # invalid, will raise a TypeError
>>> pow(2, y=10)  # invalid, will raise a TypeError
```

Another example:

```python
def table_format(items, separator=',', /, prettify=False):
    pass
```
* Once a positional-only parameter is specified with a default, the following positional-only and positional-or-keyword parameters need to have defaults as well (in this case, `prettify`).
* Positional-only parameters which do not have default values are `required` positional-only parameters (in this case, `items`).


### The generic case
A function definition, with all flavors of argument-passing variants, would look like this:

``` python
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        Positional or keyword   |
        |                                - Keyword only
         -- Positional only
```

## When to use positional-only arguments

* Use positional-only if names do not matter or have no meaning, and there are only a few arguments which will always be passed in the same order.
* Use keyword-only when names have meaning and the function definition is more understandable by being explicit with names.


Positional-only arguments were proposed in [PEP 570](https://www.python.org/dev/peps/pep-0570/), and it's worth taking a look at the doc to understand the feature in more detail.

---

_Originally posted on [DeepSource Blog](https://deepsource.io/blog/python-positional-only-arguments/). Join the discussion on [reddit](https://www.reddit.com/r/Python/comments/cvmoad/positionalonly_arguments_in_python/)._