Thanks to visit codestin.com
Credit goes to www.pythonmorsels.com

Unpacking arbitrary keyword arguments into a function call PREMIUM

Series: Functions
Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
3 min. read Watch as video Python 3.10—3.14
Python Morsels
Watch as video
03:22

In Python, you can pass arbitrary keyword arguments to functions.

Unpacking an iterable of positional arguments

You may have seen an asterisk (*) used to unpack an iterable into separate positional arguments in a function call:

>>> numbers = [2, 1, 3, 4, 7, 11, 18]
>>> print(*numbers)
2 1 3 4 7 11 18

You may have also seen an asterisk used in a function definition to make a function that accepts any number of positional arguments:

>>> from random import randint
>>> def roll(*dice):
...     return sum(randint(1, die) for die in dice)
...
>>> roll(6, 6)
7

What if you wanted to do the same thing but with keyword arguments?

Note: If you're not sure what keyword arguments are, see positional and keyword arguments.

Capturing all keyword arguments provided to a function

When defining a function, you can use double asterisk (**) to capture all keyword arguments that are provided to the function when it's called:

def print_properties(**properties):
    for name, value in properties.items():
        print(f"{name}: {value}")

We can call that function with any keyword argument that we can think up:

>>> print_properties(color="purple", size="L", price=5)
color: purple
size: L
price: 5

We just called that function with the keyword arguments color, size, and price, but any argument names would be accepted.

Passing arbitrary keyword arguments to a function

Just as * has one use in function definitions and one use in function calls, ** can also be used in both function definitions and in function calls. When calling a function, you can use ** to unpack a dictionary of key-value pairs into separate keyword arguments.

Here we're passing the sep and end arguments to this print call:

>>> kwargs = {"sep": ", ", "end": "!\n"}
>>> numbers = [2, 1, 3, 4, 7, 11]
>>> print(*numbers, **kwargs)
2, 1, 3, 4, 7, 11!

That's the same as this code, which hard-codes those keyword arguments:

>>> numbers = [2, 1, 3, 4, 7, 11]
>>> print(*numbers, sep=", ", end="!\n")
2, 1, 3, 4, 7, 11!

It's usually better to hardcode keyword arguments, because code is usually more readable this way.

But there are times when dynamic keyword arguments can be very handy.

When are dynamic keyword arguments used?

Using ** can be handy with functions that accept any keyword arguments, like the print_properties function that we made before:

>>> values = {"color": "purple", "size": "L"}
>>> print_properties(**values)
color: purple
size: L

Note that when using **, the keys in the given dictionary must be strings, and they'll typically be strings that represent valid Python variable names.

Using ** with the string format method

These two uses of **, one for function calling and one for function definitions, often go together.

For example, you'll see ** unpacking used with functions that accept arbitrary keyword arguments, like the string format method.

The format method accepts any keyword arguments that we can provide, and it matches the names of those arguments to any named replacement groups within the template string that we're operating on:

>>> base_url = "https://api.stackexchange.com/2.3/questions/{ids}?site={site}"
>>> context = {"ids": "33809864;2759323;9321955", "site": "stackoverflow"}
>>> url_for_questions = base_url.format(**context)
>>> url_for_questions
'https://api.stackexchange.com/2.3/questions/33809864;2759323;9321955?site=stackoverflow'

So ** keyword argument unpacking is often used with functions like the string format method that accept arbitrary keyword arguments.

Using ** with super for easier class inheritance

But the most common use for ** is within class inheritance.

Here we have a class that inherits from another class:

from django import forms


class ContactForm(forms.Form):
    name = forms.CharField(max_length=100)
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea)

    def __init__(self, *args, user=None, **kwargs):
        super().__init__(*args, **kwargs)
        if user and user.is_authenticated:
            self.fields["email"].initial = user.email

Note that both * and ** are being used in this method to capture all positional and keyword arguments that are provided in each call to this initializer method.

Those captured arguments are then unpacked into positional and keyword arguments provided to the parent class's method with the same name:

class ContactForm(forms.Form):
    ...
    def __init__(self, *args, user=None, **kwargs):
        super().__init__(*args, **kwargs)
        ...

Using * and ** for class inheritance allows us to capture all the arguments given to our method and pass them up to our parent class. This is a way for our method to delegate the responsibility of accepting those arguments to our parent.

Use ** for keyword argument packing and unpacking in Python

Those are the two uses of ** in Python:

  1. In a function definition, ** allows that function to accept any keyword argument that might be given to it
  2. In a function call, ** is for unpacking a dictionary of key-value pairs into the keyword arguments that are given to that function

The most common use of ** is in class inheritance.

Series: Functions

Python, like many programming languages, has functions. A function is a block of code you can call to run that code.

Python's functions have a lot of "wait I didn't know that" features. Functions can define default argument values, functions can be called with keyword arguments, and functions can be written to accept any number of arguments.

To track your progress on this Python Morsels topic trail, sign in or sign up.

0%
Python Morsels
Watch as video
03:22
This is a free preview of a premium screencast. You have 2 previews remaining.