Skip to content

Splat Operator

The splat operators * and ** let a function accept a variable number of positional or keyword arguments. *args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. The same operators also unpack a sequence or mapping into a function call β€” that dual use is what confuses people first.

Without splats, a function can only accept a fixed, known set of parameters. To support β€œany number of arguments,” older code had to accept a single list or dict and require callers to build it explicitly:

def total(numbers_list):
return sum(numbers_list)
total([1, 2, 3, 4]) # caller must remember to wrap in a list
def total(*args):
return sum(args)
total(1, 2, 3, 4) # args == (1, 2, 3, 4)
def build_profile(**kwargs):
return kwargs
build_profile(name="Alice", age=30) # kwargs == {'name': 'Alice', 'age': 30}
# Combined, in the required order: positional, *args, keyword-only, **kwargs
def request(url, *args, timeout=10, **kwargs):
print(url, args, timeout, kwargs)
request("/api", "extra", retries=3)
# /api ('extra',) 10 {'retries': 3}

Splats also unpack on the call side:

def add(a, b, c):
return a + b + c
nums = [1, 2, 3]
add(*nums) # unpacks list into positional args -> 6
opts = {"a": 1, "b": 2, "c": 3}
add(**opts) # unpacks dict into keyword args -> 6
  • Flexible APIs: Functions like print(), max(), and dict.update() accept any number of arguments because they’re built on *args/**kwargs.
  • Wrapper functions: Decorators forward arbitrary arguments to the wrapped function without knowing its signature (def wrapper(*args, **kwargs): return func(*args, **kwargs)).
  • No dummy containers: Callers pass arguments naturally instead of manually building a list/dict just to satisfy the function signature.
  • *args is a tuple, **kwargs is a dict β€” inside the function, not at the call site.
  • The names args/kwargs are convention only; *values, **options works identically.
  • Order in a def matters: positional params β†’ *args β†’ keyword-only params β†’ **kwargs.
  • You can’t have two *args-style catches in one signature, but you can mix explicit keyword-only args after *args.
  • Unpacking too few/many items into fixed positional parameters (a, b = *[1,2,3]-style mistakes) raises TypeError, not a silent truncation.
def f(a, *, b, **kwargs): # b is keyword-only after the bare *
print(a, b, kwargs)
f(1, b=2, c=3) # 1 2 {'c': 3}
f(1, 2) # TypeError: f() takes 1 positional argument but 2 were given
  1. What type is args inside def f(*args): ... when called as f(1, 2, 3)?

    AnswerA tuple: `(1, 2, 3)`.
  2. Given def greet(**kwargs): ..., how do you call it by unpacking d = {"name": "Sam"}?

    Answer`greet(**d)` β€” the double-star unpacks the dict into keyword arguments.
  3. Why must **kwargs always come last in a function signature?

    AnswerBecause everything after a bare positional/`*args` collection point must be identifiable by keyword, and `**kwargs` is the catch-all for *any* remaining keyword β€” nothing can meaningfully follow it.