Variable-Length Arguments

*args and **kwargs let a function accept any number of positional or keyword arguments, turning fixed signatures into flexible, forwardable ones.

Sometimes you don't know in advance how many arguments a function needs to accept — a print-like function, a mathematical sum_all, or a wrapper that forwards whatever it receives to another function. Python solves this with *args (collects extra positional arguments into a tuple) and **kwargs (collects extra keyword arguments into a dict). The same */** syntax also *unpacks* collections back into individual arguments at a call site, making packing and unpacking two sides of one mechanism.

*args: Packing Positional Arguments

A parameter prefixed with * collects any extra positional arguments into a tuple. The name args is convention, not a keyword — any valid identifier works, but args is what everyone expects.

def total(*numbers):
    return sum(numbers)

print(total(1, 2, 3))       # 6
print(total(10, 20))        # 30
print(total())               # 0 — numbers is an empty tuple

print(type(total.__code__.co_varnames))  # 'numbers' is bound to a tuple inside the function

**kwargs: Packing Keyword Arguments

A parameter prefixed with ** collects any extra keyword arguments into a dict, keyed by the argument names used at the call site.

Because dicts preserve insertion order (a language guarantee since 3.7), **kwargs iterates in the order the caller supplied the keyword arguments.

def describe_pet(**traits):
    for key, value in traits.items():
        print(f"{key}: {value}")

describe_pet(name="Milo", species="cat", age=3)
# name: Milo
# species: cat
# age: 3

Combining Fixed, *args, and **kwargs

A single signature can mix required parameters, *args, and **kwargs — in that order. This lets a function require certain arguments while still accepting arbitrary extras.

def build_url(base, *path_parts, **query_params):
    path = "/".join(path_parts)
    query = "&".join(f"{k}={v}" for k, v in query_params.items())
    url = f"{base}/{path}"
    return f"{url}?{query}" if query else url

print(build_url("https://api.example.com", "users", "42", verbose="true", page=2))
# https://api.example.com/users/42?verbose=true&page=2

Packing: What Goes Into args and kwargs

'Packing' is the *def*-side behavior: extra positional arguments are gathered ('packed') into the *args tuple, and extra keyword arguments are packed into the **kwargs dict, automatically, by Python's call machinery.

def show(*args, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)

show(1, 2, x=3, y=4)
# args: (1, 2)
# kwargs: {'x': 3, 'y': 4}

Unpacking: The Reverse Direction at a Call Site

'Unpacking' is the *call*-side behavior: * spreads an existing iterable into separate positional arguments, and ** spreads an existing mapping into separate keyword arguments.

def add3(a, b, c):
    return a + b + c

values = [1, 2, 3]
print(add3(*values))          # unpack list -> 3 positional args -> 6

options = {"a": 10, "b": 20, "c": 30}
print(add3(**options))        # unpack dict -> 3 keyword args -> 60

# Multiple unpacking is allowed (3.5+):
more = [4, 5, 6]
print([*values, *more])       # [1, 2, 3, 4, 5, 6]
merged = {**options, "d": 40}
print(merged)                  # {'a': 10, 'b': 20, 'c': 30, 'd': 40}

Forwarding Arguments

A very common pattern is a wrapper function that accepts *args, **kwargs purely to forward them, untouched, to another function — used heavily in decorators, subclass __init__ methods calling super().__init__, and logging/proxy wrappers.

import functools

def log_call(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        return func(*args, **kwargs)   # forward everything unchanged
    return wrapper

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

print(add(2, 3))
# Calling add with args=(2, 3), kwargs={}
# 5

Practical Usage

*args/**kwargs show up constantly: variadic math functions (min, max-style), decorators that must work on any wrapped function, class hierarchies where a subclass forwards unknown keyword arguments to its parent, and configuration functions accepting an open-ended set of options.

class Base:
    def __init__(self, name, **options):
        self.name = name
        self.options = options

class Widget(Base):
    def __init__(self, name, color="blue", **kwargs):
        super().__init__(name, **kwargs)  # forward whatever Base still needs
        self.color = color

w = Widget("button", color="red", size="large", disabled=False)
print(w.color, w.options)   # red {'size': 'large', 'disabled': False}

Edge Cases

Unpacking an object that isn't iterable (for *) or isn't a mapping with string keys (for `) raises a TypeError. Duplicate keys between kwargs` unpacking and an explicit keyword argument raise 'got multiple values'.

def f(a, b):
    return a + b

f(*5)                       # TypeError: argument after * must be an iterable
f(**{1: "x"})                # TypeError: keywords must be strings
f(a=1, **{"a": 2})           # TypeError: got multiple values for keyword argument 'a'

Mental Model

Think of * and ** as a single 'spread/gather' operator that changes direction depending on where it appears: in a def, it *gathers* loose arguments into a container; in a call, it *spreads* a container back out into loose arguments.

Quick Reference

In a def:            *args  -> tuple of extra positional args
                     **kwargs -> dict of extra keyword args

At a call:           f(*seq)    -> spread seq as positional args
                     f(**mapping) -> spread mapping as keyword args

Order in a def:      required, *args, keyword-only, **kwargs

Key Takeaways

*args packs extra positional arguments into a tuple inside the function; **kwargs packs extra keyword arguments into a dict.

The same */** syntax at a call site does the opposite: it unpacks a sequence or mapping back into individual arguments.

Parameters after a bare *args become keyword-only automatically.

*args, **kwargs wrapper signatures are the standard way to forward arbitrary arguments through decorators and subclass constructors.

Unpacking a non-iterable with *, or a non-string-keyed mapping with **, raises a TypeError.

def wrapper(*args, **kwargs): return func(args, kwargs) # forgot to unpack again def wrapper(*args, **kwargs): return func(*args, **kwargs)

Passing the packed tuple and dict directly (without re-unpacking with * and **) sends func exactly two arguments — a tuple and a dict — instead of forwarding the original arguments.

def f(a, **kwargs, *args): ...

def f(a, *args, **kwargs): ...

**kwargs must always be the last item in a parameter list; *args (if present) comes before it. The wrong order is a SyntaxError.

def total(*args): return sum(args) total([1, 2, 3]) # passes ONE list, not three numbers total(*[1, 2, 3]) # unpack the list into three separate arguments

Passing a list directly makes args a one-tuple containing that list, not three separate numbers; unpacking with * at the call site spreads it correctly.

What does *args collect, and what data type does it become inside the function?

It collects every extra positional argument the caller supplies beyond the named parameters, and binds them as a tuple inside the function body.

What does **kwargs collect, and what data type does it become inside the function?

It collects every extra keyword argument the caller supplies beyond the named parameters, and binds them as a dict (keyed by argument name) inside the function body.

What is the difference between 'packing' and 'unpacking' with * and **?

Packing happens in a function definition: * or ** gathers loose extra arguments into a tuple or dict. Unpacking happens at a call site: * or ** spreads an existing sequence or mapping back out into individual arguments.

In def f(a, *args, b, **kwargs), is b positional or keyword-only, and why?

b is keyword-only, because any parameter listed after *args in a signature automatically becomes keyword-only — the caller must pass b=value.

Why do decorator wrapper functions almost always use def wrapper(*args, **kwargs)?

Because a decorator doesn't know the signature of the function it's wrapping in advance, so *args, **kwargs lets the wrapper accept literally any call and forward it unchanged with func(*args, **kwargs).

What error do you get from f(*5) if f is a normal function, and why?

A TypeError, because * requires an iterable to unpack, and an int (5) is not iterable.

How would you merge two dictionaries into a new one using unpacking syntax?

merged = {dict1, dict2} — later keys override earlier ones on conflict, since dict literals apply key-value pairs left to right.

Write a function multiply_all(*numbers) that returns the product of any number of arguments (1 if none are given).

from functools import reduce def multiply_all(*numbers): return reduce(lambda a, b: a * b, numbers, 1) print(multiply_all(2, 3, 4)) print(multiply_all())

24 then 1 — the product of 2*3*4 is 24, and the identity value 1 is returned when no arguments are given.

What does this print?

1 2 (3, 4) {'x': 5, 'y': 6} — a and b take the first two positional args, the rest go to args, and named ones go to kwargs.

Write a function safe_call(func, *args, **kwargs) that calls func(*args, **kwargs) and returns None instead of raising if any exception occurs.

def safe_call(func, *args, **kwargs): try: return func(*args, **kwargs) except Exception: return None print(safe_call(int, "42")) print(safe_call(int, "not a number"))

42 then None — the first call succeeds and forwards '42' to int(); the second raises ValueError internally, which safe_call catches and converts to None.

{'x': 1, 'y': 2} — both dicts can be unpacked into the same call as long as their keys don't overlap; overlapping keys would raise a TypeError.

def f(a, b, *args, **kwargs):
    print(a, b, args, kwargs)

f(1, 2, 3, 4, x=5, y=6)
def f(**kwargs):
    return kwargs

a = {"x": 1}
b = {"y": 2}
print(f(**a, **b))