kwargs
is short for “keyword arguments”, which are optional arguments of a Python function.
In Django, you may come across functions which contain *args
and/or **kwargs
as inputs.
As someone who learned Django without prior experience in Python, I found this very confusing. But once I learned how to use them, I understood how they could be useful.
However, **kwargs
should be used sparingly, when there is a clear advantage. Overusing **kwargs
in your function definitions will make your code harder to read.
I am going to show how how **kwargs
can be used followed by a few examples.
Positional Arguments vs Keyword Arguments
Positional Arguments
Consider this function:
def my_func(a, b):
return a + b
Here, a
and b
are positional arguments. They are required. If you provide fewer than two arguments, an error will be raised. The values of each argument are inferred from the order in which arguments are provided in the function call.
So if you call the function like this…
result = my_func(x, y)
The value of a
is x
because it was provided first and b
is y
because it was provided second.
Keyword arguments
Now consider this function:
def my_func(a, b, c = 0):
return a + b + c
Here, a
and b
are positional arguments and c
is a keyword argument.
Keyword arguments are optional.
I still have to call the function with at least two arguments because a
and b
are positional.
I can still call my function like this:
my_func(10, 4)
>> 14
Or I can call it like this:
my_func(10, 4, 5)
>> 19
What are **kwargs?
Kwargs are a dictionary of optional keyword arguments.
In this function, c
is a keyword argument:
def my_func(a, b, c = 0):
return a + b + c
I could also write the function like this:
def my_func(a, b, **kwargs):
c = kwargs.get("c", 0)
return a + b + c
The only difference is I have to explicitly define c
when calling the function.
my_func(10, 4, c=5)
What is the ** in **kwargs for?
The double asterisk in **kwargs
unpacks the kwargs
dictionary. It means we don’t have to write out every keyword argument when we call a function.
Consider this function:
def my_func(a, b, **kwargs):
c = kwargs.get("c", 0)
return a + b + c
Which is called here:
kwargs = {"c": 5, "d": 3}
result = my_func(10, 4, **kwargs)
Here, my_func
would receive c=5
and d=3
as if it had been called like this:
result = my_func(10, 4, c=5, d=3)
Unlimited arguments
When a function definition accepts **kwargs
as an argument, there is no limit to how many arguments the function can be called with.
If a function does not accept **kwargs
, then providing extra arguments will raise an error.
Consider this definition:
def my_func(a, b, c = 0):
return a + b + c
def my_other_func(a, b, c = 0, **kwargs):
return a + b + c
and you call the function:
kwargs = {"c": 5, "d": 0}
my_func(10, 4, **kwargs)
my_other_func(10, 4, **kwargs)
Calling my_func
will raise a TypeError
because it only allows arguments specified in the definition.
TypeError: my_func() got an unexpected keyword argument 'd'
Calling my_other_func
will not raise an error, because the function definition allows **kwargs
, which effectively allows an infinite number of keyword arguments.
Example 1: Django save() method
Here is an example of when **kwargs
are very useful.
I have a model for a blog post and I want a timestamp every time an instance of the model is saved.
To do this, I override the save()
method of the base model (django.models.Model
) so I can get the current time and save it to the updated
field, before letting Django save the field as normal.
This is my new function:
# blog/models.py
def save(self, **kwargs):
self.updated_date = datetime.now()
super().save(**kwargs)
Here, **kwargs
accepts the keyword arguments from wherever the function was called and passes it to super().save(**kwargs)
. The super calls the save method of the parent class (this is so we don’t have to completely rewrite the save function) and gives it the same arguments our custom save method was called with.
The original method has four keyword arguments: force_insert
, force_update
, using
and update_fields
.
I could have written my function like this and get the same result:
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
self.updated_date = datetime.now()
super().save(force_insert=False, force_update=False, using=None, update_fields=None)
Why **kwargs is better here
To make this work without **kwargs
, I had to look up the save
method and copy each argument and its default value.
If Django were to ever change the definition of save
on the base class, then I would have to update the function. This means I have to keep track of each keyword argument and their default values and make sure my custom function is consistent.
**kwargs
is not only more succinct, it decouples your custom method from the logic of the parent class.
Remember, the save()
method of a model can be called from several places in your application.
Example 2: Calling an API
Imagine you have a function that calls another function. If both functions can be called with the same keyword arguments, you could use **kwargs
as a shortcut.
The long way:
def get_orders(api, a = None, b = None, c = None, d = None, e = None, f = None, g = None):
return api.get(a=a, b=b, c=c, d=d, e=e, f=f, g=g)
The short way:
def get_orders(api, **kwargs):
return api.get(**kwargs)
Is this good practice?
Here, **kwargs
avoids having to list a long list of keyword arguments twice.
BUT
It is no longer clear what arguments the API was called with. This practice could be very tempting for the developer who knows the API well. For a new team member, who is trying to figure out what arguments they can call the API with, this can be very confusing.
Example 3: When **kwargs are bad practice
Consider these two function definitions.
# bad
def get_invoice(order, customer, **kwargs):
...
...
message = kwargs.get("message", "Thank you for your order")
...
...
...
template = kwargs.get("template", None)
...
...
...
return invoice
and compare it with this one:
# better
def get_invoice(order, customer, message="Thank you for your order", template=None):
...
...
return invoice
The function above doesn’t use **kwargs
and it is easier to read. This might not be clear from my example above but imagine you are working on code written by another developer. Your first job will be to understand what the function is trying to do.
Using **kwargs
hides some of the arguments. I have had to read long, procedural functions looking for kwargs.get
or kwargs.pop
statements just so I can figure out what arguments the function could be called with.
Readability is a priority when I write code, so I use **kwargs
sparingly. In the first example, **kwargs
allowed us to extend a Django function without knowing the inner workings of the function. Here, **kwargs
has been used as a shortcut at the expense of readability.
Conclusion: when should you use **kwargs
For Django developers, **kwargs
are most useful when overriding methods provided by Django. The **kwargs
notation means you don’t have to know the logic of the method you are trying to customise. This lack of coupling means Django could change the method of the base class and you wouldn’t have to update your function.
Otherwise, I recommend using **kwargs
notation sparingly. While they can be a shortcut to avoid writing out long lists of keyword arguments, this can come at the expense of code readability.