DEV Community

loading...
Cover image for **kwargs, you got me!

**kwargs, you got me!

saveshodhan profile image Shodhan Save ・2 min read

Hello, world! 👋🏼

Being a Python developer since quite a few years now, this language still doesn't stop to teach me!

The other day I was refactoring a function that took just kwargs as a parameter. We wanted to make functions more readable, so we thought of having few important entries from kwargs as positional arguments.

So this:

def foo(**kwargs):
    print(kwargs)
Enter fullscreen mode Exit fullscreen mode

turned into this:

def foo(status, **kwargs):
    print(kwargs)
Enter fullscreen mode Exit fullscreen mode

Of course this requires that kwargs should have the status key, otherwise it would throw an error

TypeError: foo() missing 1 required positional argument: 'status'
Enter fullscreen mode Exit fullscreen mode

After making this sure, things looked pretty readable; until a test case failed:

KeyError: 'status'
Enter fullscreen mode Exit fullscreen mode

Wait. What? How?? I just changed the arguments, not a single line of code change inside the function..?
And yet, I got KeyError for kwargs['status']

At first, this seemed as if it should not have had happened. But then, it did! Going back to the REPL and trying out few things quickly shed light onto a thing that I didn't know about kwargs till that day.

Consider the below code:

>>> d = {'a': 'A', 'b': 'B'}
>>> 
>>> def foo(x, **kwargs):
...     print(x, kwargs)
... 
>>> def bar(x, c, **kwargs):
...     print(x, c, kwargs)
... 
>>> 
Enter fullscreen mode Exit fullscreen mode

Now let's see what happens when we call these functions:

>>> foo(1, **d)
1 {'a': 'A', 'b': 'B'}
>>> 
Enter fullscreen mode Exit fullscreen mode
>>> bar(1, **d)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bar() missing 1 required positional argument: 'c'
>>> 
Enter fullscreen mode Exit fullscreen mode

This error is the same as mentioned above. We solve this by making sure the key is present in the dict:

>>> d['c'] = 'C'
>>> 
Enter fullscreen mode Exit fullscreen mode

And call the function again:

>>> bar(1, **d)
1 C {'a': 'A', 'b': 'B'}
>>> 
Enter fullscreen mode Exit fullscreen mode

Notice what happens when we print kwargs - the key that we accepted as a positional argument (c) got popped out of kwargs! It was no more part of kwargs, which explains why we got the KeyError above - because before refactoring, the key status was a part of kwargs, which, after refactoring, was coming in as an individual argument and was no more a part of kwargs.

It's a small thing, but can go unnoticed. Thanks to test cases, we were saved from creating another incident report..

Discussion (1)

pic
Editor guide
Collapse
saveshodhan profile image
Shodhan Save Author

On a side note:

  • if you observe, the key in the kwargs dict has to be the name of the positional argument. So, if status is supposed to be the positional argument, then kwargs is expected to have the same key
  • this also means you cannot have, say, integers type keys as arguments because arguments cannot be integers. The below is not allowed:

def foo(x, 1, **kwargs):