Monday, November 30, 2020

Python Morsels: Keyword-Only Function Arguments

Related article:

Transcript:

Let's define a function that accepts a keyword-only argument.

Accepting Multiple Positional Argument

This greet function accepts any number of positional arguments:

>>> def greet(*names):
...     for name in names:
...         print("Hello", name)
...

If we give it some names, it's going to print out Hello, and then the name, for each of those names:

>>> greet("Trey", "Jo", "Ian")
Hello Trey
Hello Jo
Hello Ian

It does this through the * operator, which is capturing all the positional arguments given to this function.

Positional and Keyword-Only Argument

If we wanted to allow the greeting (Hello) to be customized we could accept a greeting argument:

>>> def greet(*names, greeting):
...     for name in names:
...         print(greeting, name)
...

We might try to call this new greet function like this:

>>> greet("Trey", "Hi")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: greet() missing 1 required keyword-only argument: 'greeting'

But that gives us an error. The error says that greet is missing one required keyword-only argument greeting.

That error is saying is that greeting is a required argument because it doesn't have a default value and it must be specified as a keyword argument when we call this function.

So if we want to customize greeting, we can pass it in as a keyword argument:

>>> greet("Trey", greeting="Hi")
Hi Trey
>>> greet("Trey", greeting="Hello")
Hello Trey

We probably want greeting to actually have a default value of Hello. We can do that by specifying a default value for the greeting argument:

>>> def greet(*names, greeting="Hello"):
...     for name in names:
...         print(greeting, name)
...
>>> greet("Trey", "Jo")
Hello Trey
Hello Jo

Because greeting is after that *names in our function definition, Python sees greeting as a keyword-only argument: an argument that can only be provided as a keyword argument when this function is called.

It can only be given by its name like this:

>>> greet("Trey", "Jo", greeting="Hi")
Hi Trey
Hi Jo

Keyword-Only Arguments in Built-in Functions

This is actually something you'll see in some of Python's built-in functions. For example, the print function accepts any number of positional arguments, as well as four optional keyword-only arguments: sep, end, file, and flush:

>>> help(print)
Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

Note that the documentation for print doesn't use the * syntax, but that ... is print's way of indicating that it accepts any number of values and then all of the arguments after that must be keyword arguments.

If we look at the documentation for greet, you'll see how keyword-only arguments usually show up in documentation:

>>> help(greet)
Help on function greet in module __main__:

greet(*names, greeting='Hello')

Everything after that * (greeting in this case), can only be specified as a keyword argument.

Keyword-Only Arguments without Capturing All Positional Arguments

It is also possible to make a function that doesn't capture any number of positional arguments, but does have some keyword-only arguments. The syntax for this is really weird.

Let's make a multiply function that accepts x and y arguments:

>>> def multiply(*, x, y):
...     return x * y
...

That lone * before x and y means that they must be specified as keyword arguments.

So, if we were to try to call multiply with two positional arguments, we'll get an error:

>>> multiply(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: multiply() takes 0 positional arguments but 2 were given

To call this function, we have to specify x and y as keyword arguments:

>>> multiply(x=1, y=2)
2

If we call this function with nothing you'll see an error message similar to what we saw before about required keyword-only arguments:

>>> multiply()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: multiply() missing 2 required keyword-only arguments: 'x' and 'y'

Keyword-Only Arguments in the Standard Library

You'll actually sometimes see this * thing on its own within the Python standard library. For example in thechown function in the os module (used for changing the ownership of a file) uses the a lone * to specify keyword-only arguments:

chown(path, uid, gid, *, dir_fd=None, follow_symlinks=True)
    Change the owner and group id of path to the numeric uid and gid.

The chown function documentation shows path, uid, gid, and then a * (which isn't an argument itself), and then dir_fd and follow_symlinks. That lone * is a way of noting that everything after that point is a keyword-only argument.

The last two arguments, dir_fd and follow_symlinks can only be specified by their name when the chown function is called.

Summary

So, whenever you see a function that uses * to capture any number of positional arguments (e.g. *args in the function definition), note that any arguments defined after that * capturing can only be specified as a keyword argument (they're keyword-only arguments).

Also if you see a function that has an * on its own with a comma after it, that means that every argument after that point, is a keyword only argument it must be specified by its name when that function is called.



from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...