14

The phrase "keyword only args" in Python is a bit ambiguous - usually I take it to mean args passed in to a **kwarg parameter. However, the inspect module seems to make a distinction between **kwarg and something called "keyword only arguments".

From the docs:

inspect.getfullargspec(func)

Get the names and default values of a Python function’s arguments. A named tuple is returned:

FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations)

args is a list of the argument names. varargs and varkw are the names of the * and ** arguments or None. defaults is an n-tuple of the default values of the last n arguments, or None if there are no default arguments. kwonlyargs is a list of keyword-only argument names. kwonlydefaults is a dictionary mapping names from kwonlyargs to defaults. annotations is a dictionary mapping argument names to annotations.

So the inspect module has something called kwonlyargs and kwonlydefaults. What does this mean in an actual function signature? If you have a function signature that accept a **kwarg argument, you can't really know the names of the keyword arguments until runtime, because the caller can basically just pass any arbitrary dictionary. So, what meaning does kwonlyargs have in the context of a function signature - which is what the inspect.getfullargspec provides.

Siler
  • 8,976
  • 11
  • 64
  • 124

1 Answers1

18

TL;DR: Keyword-only arguments are not the same as normal keyword arguments.


Keyword-only arguments are arguments that come after *args and before **kwargs in a function call. As an example, consider this generic function header:

def func(arg, *args, kwonly, **kwargs):

In the above, kwonly takes a keyword-only argument. This means that you must supply its name when giving it a value. In other words, you must explicitly write:

func(..., kwonly=value, ...)

instead of just passing a value:

func(..., value, ...)

To explain better, consider this sample call of the function given above:

func(1, 2, kwonly=3, kw=4)

When Python interprets this call, it will:

  • Assign arg to 1 because its position in the function signature matches the position of 1 in the call.

  • Place 2 in *args because *args collects any extra positional arguments and 2 is extra.

  • Assign kwonly to 3 because we have (as is necessary) explicitly told it to. Note that if we had done this instead:

    func(1, 2, 3, kw=4)
    

    3 would also be placed in *args and a TypeError would be raised for not supplying an argument to kwonly (since we did not give it a default value in this case).

  • Place kw=4 in **kwargs because it is an extra keyword argument, which are collected by **kwargs.

Below is a demonstration of what I said above:

>>> def func(arg, *args, kwonly, **kwargs):
...     print('arg:', arg)
...     print('args:', args)
...     print('kwonly:', kwonly)
...     print('kwargs:', kwargs)
...
>>> func(1, 2, kwonly=3, kw=4)
arg: 1
args: (2,)
kwonly: 3
kwargs: {'kw': 4}
>>>
>>> func(1, 2, 3, kw=4)  # Should have written: 'kwonly=3'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() missing 1 required keyword-only argument: 'kwonly'
>>>

Basically, you can look at keyword-only arguments as keyword arguments where you must supply the name of the parameter when giving them a value. A positional value will not suffice, as with normal keyword arguments.

>>> def func(kw=None):
...     print('kw:', kw)
...
>>> func(kw=1)
kw: 1
>>> func(1)  # Do not need the name in this case.
kw: 1
>>>
>>> def func(*, kwonly=None):
...     print('kwonly:', kwonly)
...
>>> func(kwonly=1)
kwonly: 1
>>> func(1)  # Always need the name with keyword-only arguments.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() takes 0 positional arguments but 1 was given
>>>

Finally, I know that some people are thinking "Why have keyword-only arguments anyways?" The answer is simply that they make things more readable in some cases (especially with functions that take a variable number of arguments).

As an example, consider the built-in max function and its keyword-only key argument. What is more readable to you? Doing something like this:

max(lambda x: -x, arg1, arg2, arg3)

and having people remember that the first argument to max is always the key function or doing this:

max(arg1, arg2, arg3, key=lambda x: -x)

and making it clear to everyone that lambda x: -x is your key function. Plus, making key a keyword-only argument allows you to simply omit the key function if you do not need one:

max(arg1, arg2, arg3)

instead of doing:

max(None, arg1, arg2, arg3)

For more information, you can check out these sources: