Sometimes you don't know in advance how many arguments a function will be called with. Python's *args and **kwargs let a function accept any number of positional arguments and any number of keyword arguments, without you having to list every possible parameter name up front. You'll see this pattern constantly in library code — logging helpers, wrapper/decorator functions, and anything designed to be flexible.
Using *args for Extra Positional Arguments
def total(*args):
# args is a tuple of every positional argument passed in
print(f"Received: {args}")
return sum(args)
print(total(1, 2, 3))
print(total(10, 20, 30, 40))
Output:
Received: (1, 2, 3)
6
Received: (10, 20, 30, 40)
100
Using **kwargs for Extra Keyword Arguments
def build_profile(**kwargs):
# kwargs is a dict of every keyword argument passed in
for key, value in kwargs.items():
print(f"{key}: {value}")
build_profile(name="Alice", age=28, city="New York")
Output:
name: Alice
age: 28
city: New York
Combining Regular Arguments, *args, and **kwargs
def describe_event(event_name, *attendees, **details):
print(f"Event: {event_name}")
print(f"Attendees: {list(attendees)}")
print(f"Details: {details}")
describe_event("Team Meeting", "Alice", "Bob", "Charlie", room="204", duration="1h")
Output:
Event: Team Meeting
Attendees: ['Alice', 'Bob', 'Charlie']
Details: {'room': '204', 'duration': '1h'}
The names args and kwargs are just convention — what actually matters is the * and **. A single * collects any extra positional arguments into a tuple; a double ** collects any extra keyword arguments into a dictionary. Python also requires a strict order when combining them: regular named parameters first, then *args, then **kwargs — reversing that order is a syntax error.
This same */** syntax also works in reverse, to unpack a list or dictionary into a function call — e.g. total(*[1, 2, 3]) or build_profile(**{"name": "Bob"}) — which is exactly how a lot of "pass-through" wrapper functions and decorators forward arguments to the function they're wrapping.
