More Kwargs Please!
I really dislike functions which accept more than 2 or 3 positional arguments, especially if all of those arguments are of the same type. Something like this:
def send_request(
url: str,
method: str,
content_type: str,
body: str,
) -> None:
...
send_request(
"http://example.com",
"GET",
"text/plain",
"This is a test!",
)
Positional arguments are the arguments of a function who’s position in the list is significant. Put another way, if you flipped two arguments in a function the meaning would be different. In the example code, all of the arguments can be thought of as positional and if you flipped the
url
andmethod
arguments things would break because you would be trying to use the URL as an HTTP method and vice versa.
It would be super easy for someone to mistakenly reorder the arguments
(if you were switching from Go to Python this would common since
http.Client.get
puts the method before the URL) and you wouldn’t
get type checking help since everything is a string. Depending on the
data you may not even be able to obviously pick out that something was
entered wrong. Sure, in this example you can probably tell a URL apart
from an HTTP method but if these were opaque strings representing an
ID then good luck.
Opaque string types are when you have a string who’s value explicitly tells you nothing about what it represents and you shouldn’t read into it’s meaning. We see this a lot when we use UUID’s as identifiers. A UUID isn’t going to tell you if this is an ID for a user, a part number, or just a random jumble of characters.
The bigger this list of arguments gets to the function the more unwieldy things become and sometimes it makes more sense to have more arguments than to switch to a factory pattern or something like it.
It’s also riskier to make changes to the signature of a function (this
is more of a problem for some languages over others but it can happen
anywhere). For example, if we were working in JavaScript and we
removed the content_type
argument (maybe we can always infer the
content type is going to be application/json
now). JavaScript allows
for the caller to give more arguments than the signature calls for and
the excess is just ignored. In our case that means that the HTTP
request wouldn’t actually send the content for the body that we
expect. It would send text/plain
as the request body and the actual
body you gave would be ignored!
But what’s the alternative?
Keyword arguments (kwargs)⌗
I was first introduced to the concept of keyword arguments when I
started learning Python. The basic idea is that instead of just
listing arguments and referencing them by their position, you instead
explicitly give the name of the argument and it’s value. Let’s look
at an alternative way we can call the send_request
function using
keyword arguments instead of positional arguments.
send_request(
url="http://example.com",
method="GET",
content_type="text/plain",
body="This is a test!",
)
This looks very similar to what we did in the first example but the big difference is that each argument is labeled with exactly what it is. It would be really hard at this point to confuse what each string is for (assuming that the argument names are good) and if something was put in as the wrong argument you could seeit much more easily.
url = "http://example.com"
send_request(
url="GET",
method=url,
content_type="text/plain",
body="This is a test!",
)
But wait! I use Jetbrains for my IDE and it tells me what each positional argument is already!
Cool, but not everyone is going to be looking at the code in a Jetbrains editor. Maybe they’re using VSCode, maybe they are using Jetbrains but with different settings, or maybe they’re looking at the code in the browser as part of a PR. You just don’t know so you should try and account for that.
One of the other cool things you can do with keyword arguments is that you can move them around without breaking anything! Keyword arguments don’t depend on the position so if you think that a different order for the values makes more sense or better conveys it’s purpose, you can reorder them however you like. That’s a huge convenience that can be overlooked.
OK but that’s Python…⌗
Sure, kwargs is a Python concept but it can be used in other languages without much difficulty. Here’s a few examples in other languages.
Before you say anything, yeah some of these are going to be longer than if you just used positional arguments but you’re adding a little bit of extra code but in exchange you’re getting more clarity and more flexibility.
JavaScript/Typescript⌗
interface SendRequestOptions {
url: string;
method: string;
contentType: string;
body: string;
}
function sendRequest({
url,
method,
contentType,
body,
}: SendRequestOptions) {
// Snip...
}
sendRequest({
url: "http://example.com",
method: "GET",
contentType: "text/plain",
body: "This is a test!",
});
Go⌗
type SendRequestOptions struct {
URL string
Method string
ContentType string
Body string
}
func SendRequest(options SendRequestOptions) error {
// Snip...
}
Java⌗
class Example {
public static final class SendRequestOptions {
public final String url;
public final String method;
public final String contentType;
public final String body;
}
public static void sendRequest(options SendRequestOptions) {
// Snip
}
}
You can see with how verbose Java is and how limiting the syntax is why factories/builders became popular. Creating a new type is super heavy and requires a lot of ceremony.
Summary⌗
Hopefully it’s clear why having keyword arguments is a super helpful tool when you have more than a couple of arguments on a function or where the typing on the arguments doesn’t help the consumer of your function. In most languages it’s easy enough to do. I don’t think I would suggest using kwargs everywhere but definitely any situation where there could be ambiguity.