Understanding Contiguous vs Non-Contiguous Tensors in PyTorch
Data Science and Machine Learning Cheat Sheets
Show all

Positional-only and Keyword-only arguments in Python

4 mins read

In this lesson, you’ll learn about how to add positional-only arguments to your functions in Python 3.8.

The built-in function float() can be used for converting text strings and numbers to float objects. Consider the following example:

>>> float("3.8")

>>> help(float)
class float(object)
 |  float(x=0, /)
 |  Convert a string or number to a floating point number, if possible.

Look closely at the signature of float(). Notice the slash (/) after the parameter. What does it mean?

It turns out that while the one parameter of float() is called x, you’re not allowed to use its name:

>>> float(x="3.8")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float() takes no keyword arguments

When using float(), you’re only allowed to specify arguments by position, not by keyword. Before Python 3.8, such positional-only arguments were only possible for built-in functions. There was no easy way to specify that arguments should be positional-only in your own functions:

>>> def incr(x):
...     return x + 1
>>> incr(3.8)

>>> incr(x=3.8)

In Python 3.8, you can use / to denote that all arguments before it must be specified by position. You can rewrite incr() to only accept positional arguments:

>>> def incr(x, /):
...     return x + 1
>>> incr(3.8)

>>> incr(x=3.8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: incr() got some positional-only arguments passed as
           keyword arguments: 'x'

By adding / after x, you specify that x is a positional-only argument. You can combine regular arguments with positional-only ones by placing the regular arguments after the slash:

>>> def greet(name, /, greeting="Hello"):
...     return f"{greeting}, {name}"
>>> greet("Christopher")
'Hello, Christopher'

>>> greet("Christopher", greeting="Awesome job")
'Awesome job, Christopher'

>>> greet(name="Christopher", greeting="Did it work?")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    greet(name="Christopher", greeting="Did it work?")
TypeError: greet() got some positional-only arguments passed as
           keyword arguments: 'name'

In greet(), the slash is placed between name and greeting. This means that name is a positional-only argument, while greeting is a regular argument that can be passed either by position or by keyword.

At first glance, positional-only arguments can seem a bit limiting and contrary to Python’s mantra about the importance of readability. You may find that there are not a lot of occasions where positional-only arguments improve your code.

However, in the right circumstances, positional-only arguments can give you some flexibility when you’re designing functions. First, positional-only arguments make sense when you have arguments that have a natural order but are hard to give good, descriptive names to. Another possible benefit of using positional-only arguments is that you can more easily refactor your functions. In particular, you can change the name of your parameters without worrying that another code depends on those names.

Positional-only arguments nicely complement keyword-only arguments. In any version of Python 3, you can specify keyword-only arguments using the star (*). Any argument after * must be specified using a keyword:

>>> def to_fahrenheit(*, celsius):
...     return 32 + celsius * 9 / 5
>>> to_fahrenheit(40)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: to_fahrenheit() takes 0 positional arguments but 1 was given

>>> to_fahrenheit(celsius=40)

celsius is a keyword-only argument, so Python raises an error if you try to specify it based on position, without the keyword.

You can combine positional-only, regular, and keyword-only arguments by specifying them in this order separated by / and *. In the following example, text is a positional-only argument, border is a regular argument with a default value, and width is a keyword-only argument with a default value:>>>

>>> def headline(text, /, border="~", *, width=50):
...     return f" {text} ".center(width, border)

Since text is positional-only, you can’t use the keyword text:>>>

>>> headline("Positional-only Arguments")
'~~~~~~~~~~~ Positional-only Arguments ~~~~~~~~~~~~'

>>> headline(text="This doesn't work!")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: headline() got some positional-only arguments passed as
           keyword arguments: 'text'

border, on the other hand, can be specified both with and without the keyword:>>>

>>> headline("Python 3.8", "=")
'=================== Python 3.8 ==================='

>>> headline("Real Python", border=":")
':::::::::::::::::: Real Python :::::::::::::::::::'

Finally, width must be specified using the keyword:>>>

>>> headline("Python", "@", width=38)
'@@@@@@@@@@@@@@@ Python @@@@@@@@@@@@@@@'

>>> headline("Python", "@", 38)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    headline("Python", "@", 38)
TypeError: headline() takes from 1 to 2 positional arguments
           but 3 were given

For more details on this topic, check out the following resources:

  • PEP 457 provides an in-depth discussion on the / notation.
  • PEP 570 gives more details on Python positional-only parameters.
Amir Masoud Sefidian
Amir Masoud Sefidian
Machine Learning Engineer

Comments are closed.