def describe(*args, **kwargs):
print(args) # tuple of positional args
print(kwargs) # dict of keyword args
describe(1, 2, name="Alice") # (1, 2) {'name': 'Alice'}
nums = [1, 2, 3]
print(*nums) # unpacks: print(1, 2, 3)
def add(a, b, c):
return a + b + c
add(*[1, 2, 3]) # unpacking a list into positional args: 6
add(**{"a": 1, "b": 2, "c": 3}) # unpacking a dict into keyword args: 6
*args and **kwargs are just conventional names — the * and ** are what matter, not the identifiers. They're most often seen in wrapper functions and decorators (below) that need to forward an arbitrary call signature through to another function without knowing its exact parameters ahead of time.
first, *middle, last = [1, 2, 3, 4, 5]
# first = 1, middle = [2, 3, 4], last = 5
a, b = b, a # the classic swap — the right side builds a tuple first, then unpacks it