What are *args and **kwargs in Python?

*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict - both allow functions to accept variable numbers of arguments.

Python

Overview

`*args` and `**kwargs` are conventions for accepting variable numbers of arguments. The `*` and `**` are the actual syntax - `args` and `kwargs` are just the conventional names (you can call them anything). They are used extensively in Python frameworks and when wrapping or decorating functions.

Key Points

  • •`*args` in a function signature: collects extra positional args into a tuple.
  • •`**kwargs` in a function signature: collects extra keyword args into a dict.
  • •Order in signature: `(pos_args, *args, keyword_only, **kwargs)`.
  • •Calling a function: `*iterable` unpacks as positional args; `**dict` unpacks as keyword args.
  • •Keyword-only parameters: any parameter after `*args` must be passed by name.

Examples

*args: accept any number of positional arguments

python
def add(*numbers):
    return sum(numbers)

add(1, 2)          # 3
add(1, 2, 3, 4, 5) # 15
add()              # 0

# numbers is a tuple inside the function
def show(*args):
    print(type(args), args)
show('a', 'b')  # <class 'tuple'> ('a', 'b')

**kwargs: accept any number of keyword arguments

python
def configure(**options):
    for key, value in options.items():
        print(f"{key} = {value}")

configure(theme='dark', lang='en', debug=True)
# theme = dark
# lang = en
# debug = True

Combining regular, *args, and **kwargs

python
def log(level, *messages, separator=' | ', **metadata):
    joined = separator.join(messages)
    print(f"[{level}] {joined}", metadata)

log('INFO', 'User login', 'Session started', user_id=42, ip='127.0.0.1')
# [INFO] User login | Session started {'user_id': 42, 'ip': '127.0.0.1'}

separator is keyword-only (after *args); **metadata collects anything else.

Unpacking when calling a function

python
def greet(first, last, greeting='Hello'):
    print(f"{greeting}, {first} {last}!")

args   = ('Alice', 'Smith')
kwargs = {'greeting': 'Hi'}

greet(*args, **kwargs)   # Hi, Alice Smith!

# Very common with decorators to forward all arguments
def decorator(func):
    def wrapper(*args, **kwargs):
        print("before")
        return func(*args, **kwargs)   # forward everything to original
    return wrapper

Pro Tip

When writing a wrapper or decorator, always use `(*args, **kwargs)` to forward all arguments transparently. This ensures the wrapper works for any function it wraps, regardless of its signature.