*args & kwargs

Difficulty: Intermediate

Question

Explain *args and kwargs. How and when do you use them?

Answer

*args collects extra positional arguments into a tuple. kwargs collects extra keyword arguments into a dictionary.

Order in function signature: regular args, *args, keyword-only args, kwargs.

Common use cases: - Writing flexible APIs that accept variable arguments - Forwarding arguments to another function (decorators, wrappers) - Creating functions that work with any number of inputs

The * and operators also work for unpacking in function calls and assignments.

Code examples

*args and kwargs Basics

def flexible(*args, kwargs):
    print(f"Positional: {args}")   # tuple
    print(f"Keyword: {kwargs}")     # dict

flexible(1, 2, 3, name='Alice', age=30)

# Real-world: logging wrapper
def log_call(func):
    def wrapper(*args, kwargs):
        print(f"Calling {func.__name__}({args}, {kwargs})")
        result = func(*args, kwargs)
        print(f"Returned: {result}")
        return result
    return wrapper

@log_call
def add(a, b):
    return a + b

add(3, 5)
add(a=10, b=20)

*args captures positional arguments as a tuple, kwargs captures keyword arguments as a dict. Together they let you forward any arguments to the wrapped function.

Unpacking Operator (* and )

# * unpacking in function calls
def add(a, b, c):
    return a + b + c

nums = [1, 2, 3]
print(add(*nums))  # 6 - unpacks list into positional args

config = {'a': 10, 'b': 20, 'c': 30}
print(add(config))  # 60 - unpacks dict into keyword args

# * unpacking in assignments (Python 3)
first, *middle, last = [1, 2, 3, 4, 5]
print(first)    # 1
print(middle)   # [2, 3, 4]
print(last)     # 5

head, *tail = [1, 2, 3]
print(head)     # 1
print(tail)     # [2, 3]

# * unpacking for merging
list1 = [1, 2, 3]
list2 = [4, 5, 6]
merged = [*list1, *list2, 7]
print(merged)  # [1, 2, 3, 4, 5, 6, 7]

dict1 = {'a': 1}
dict2 = {'b': 2}
merged = {dict1, dict2, 'c': 3}
print(merged)  # {'a': 1, 'b': 2, 'c': 3}

The * and operators unpack iterables and dicts respectively. In assignments, *variable captures remaining items into a list.

Argument Order and Keyword-Only Args

# Full argument order:
# def func(positional, /, normal, *, keyword_only, kwargs)

def api_call(url, /, method='GET', *, headers=None, timeout=30):
    """url is positional-only, headers and timeout are keyword-only."""
    print(f"{method} {url}")
    print(f"Headers: {headers}, Timeout: {timeout}")

api_call('https://api.example.com')  # OK
api_call('https://api.example.com', 'POST', headers={'Auth': 'token'})
# api_call(url='...')  # Error: url is positional-only
# api_call('...', 'GET', {'Auth': 'token'})  # Error: headers is keyword-only

# Forcing keyword-only with bare *
def safe_divide(a, b, *, raise_on_zero=False):
    if b == 0:
        if raise_on_zero:
            raise ZeroDivisionError
        return None
    return a / b

print(safe_divide(10, 0))                      # None
print(safe_divide(10, 0, raise_on_zero=True))  # Raises error

Arguments after * are keyword-only: they must be passed by name. Arguments before / are positional-only. This creates clear, self-documenting APIs.

Key points

Concepts covered

*args, kwargs, Unpacking, Argument Forwarding, Variadic Functions