What **kwargs are & when to use them in your Django project

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.