Skip to content
TaeyoungKim.dev

Python *args vs **kwargs: Collecting and unpacking function arguments

PythonWritten 2 min readTaeyoungKim
LinkedInX

A notice may have one recipient or several, with options such as urgent=True supplied only when needed. The important distinction behind *args and **kwargs is simple: in a function definition, * collects extra positional arguments and ** collects extra keyword arguments.

How does Python separate extra positional and keyword arguments?

Use descriptive parameter names, recipients and options, instead of the conventional args and kwargs. The stars, rather than those names, determine the behavior.

"email" fills channel; "Ana" and "Ben" form the recipients tuple. The named urgent and retries values go into the options dictionary.

python
def prepare_notice(channel, *recipients, **options):
    return {
        "channel": channel,
        "recipients": recipients,
        "options": options,
    }

notice = prepare_notice("email", "Ana", "Ben", urgent=True, retries=2)
print(notice["recipients"])
print(notice["options"])
text
('Ana', 'Ben')
{'urgent': True, 'retries': 2}

The first positional argument, "email", binds to channel. The remaining positional values become a tuple, while the named arguments become a dictionary. The Python tutorial on arbitrary argument lists describes the same distinction.

What happens if you pass a list and dictionary unchanged?

Suppose the caller already has a list and a dictionary. Passing them directly does not unpack their contents:

python
users = ["Ana", "Ben"]
settings = {"urgent": True, "retries": 2}

notice = prepare_notice("email", users, settings)
print(notice["recipients"])
print(notice["options"])
text
(['Ana', 'Ben'], {'urgent': True, 'retries': 2})
{}

The list and dictionary are each one positional argument. Both appear inside the recipients tuple, and options is empty because no named arguments were supplied. If a whole list unexpectedly arrives as one recipient, inspect the call site.

How do stars unpack values at the call site?

If each user and setting should be passed separately, add stars when calling the function:

python
notice = prepare_notice("email", *users, **settings)
print(notice["recipients"])
print(notice["options"])
text
('Ana', 'Ben')
{'urgent': True, 'retries': 2}

The same symbols collect at definition time and unpack at call time. Check which side of a function call contains the stars.

When is a fixed parameter list better?

**options accepts unknown keyword names. A typo such as urgnet=True arrives as {'urgnet': True} unless the function validates allowed options. When the options are fixed, a signature such as def prepare_notice(channel, recipients, urgent=False, retries=0) is often clearer and exposes mistakes earlier.

Variadic arguments are useful when the accepted inputs genuinely vary, such as a wrapper forwarding arguments to another function. Their convenience does not remove the need to document and validate what the function accepts.

Key takeaways

In a definition, *args collects extra positional arguments into a tuple and **kwargs collects keyword arguments into a dictionary. In a call, *users and **settings unpack a list and dictionary into separate arguments. When a value arrives in the wrong shape, compare the stars at the definition and the call site.

Author

TaeyoungKim

Connecting technical foundations with implementation, verification, and production decisions.

#Python args kwargs#Positional arguments#Keyword arguments#Unpacking

Read next