lukeplant.me.uk

Keyword-only arguments in Python

Posted in: Python

“Keyword-only arguments” is a Python feature that was added in version 3.0. But I’ve seen and used it much less use than I could have. It is described in PEP 3102, which is pretty readable, but I think the feature could benefit from more exposure with examples and rationale.

To understand keyword-only arguments, we first have to clear up a common misunderstanding about Python positional and keyword arguments. Consider the following function:

def print_greeting(name, use_colors=False):
    # ...

We often think of this as having one positional argument and one keyword argument, and expect that calling code will use it like this:

print_greeting("Mary")
print_greeting("Mary", use_colors=True)
print_greeting("Mary", use_colors=False)  # redundant but explicit

In fact, you can also call this function like the following (although it usually surprises me if I see these forms):

print_greeting("Mary", True)
print_greeting(name="Mary")
print_greeting(use_colors=True, name="Mary")

In other words, both arguments can be passed positionally or by name. Technically you should think about positional or keyword arguments as being defined by the function call and not the function definition. (If you know your computer science terminology, the function definition defines parameters, while it is the function call that defines arguments, which has now confused me about whether the PEP should really have been titled “Keyword-only parameters”…)

The actual distinction between name and use_colors is that use_colors has a default argument value, while name does not. This does introduce limitations in how you can define and how you can call them — for example, the following is illegal:

print_greeting(use_colors=True, "Mary")

Having got that cleared up, we can now separate in our minds two different concerns:

  • When people call our function, should we encourage/expect/force them to use a keyword argument, or a positional one?
  • Do we have a good default for each of the parameters?

With Python’s keyword-only arguments we can address these two independently.

To make it impossible to pass use_colors positionally, we can define it like this:

def print_greeting(name, *, use_colors=False):
    # ...

This brings some advantages:

  • It makes the calling code more explicit (but also more verbose). It will no longer be possible to see code like:

    print_greeting("Mary", True)
    

    …and wonder “what does that boolean argument mean?”

  • It allows us as function authors to add more keyword parameters, defined in any order we want, without the possibility of breaking things for people using our functions because they were were passing arguments positionally instead of by name.

Once we have these keyword-only arguments, we can also ask “do we actually have good default values?” Let’s say we realise that the use of color codes is something that is very important and must be passed every time our print_greeting function is used — we don’t have a good default. We can write our function like this:

def print_greeting(name, *, use_colors):
    pass

And call it:

print_greeting("Mary", use_colors=True)

If the caller fails to pass use_colors, they see this error message:

TypeError: print_greeting() missing 1 required keyword-only argument: 'use_colors'

I haven’t seen keyword-only arguments that much in the wild, which I think is a shame. But now that Python 2 is much less of a concern for me, I’m going to be making much more use of this feature.

For further reading — the twin of this is positional only parameters, a more recent feature that makes it impossible to pass an argument by name.

Comments §

Comments should load when you scroll to here...