Q1What does this code print?def f(*args):
return sum(args)
print(f(1, 2, 3, 4))
Variable-Length Arguments *args / **kwargs and Multiple Return Values
Handle any number of arguments with args / *kwargs and return multiple values from one Python function — all in your browser.
In the previous article on def, you defined functions with a fixed number of arguments. In real code, though, the number of arguments isn't always known up front, and sometimes you want to return more than one result from a single call.
Python handles these with variable-length arguments and multiple return values. Both build directly on what you know about tuples and dicts.
Variable-length positional arguments *args — collect into a tuple
Put a single asterisk in front of an argument name (*args) and the function can accept any number of positional arguments. Inside the function, those arguments are available as a tuple.
The name args is the convention, but technically any descriptive name works — *prices, *messages, whatever reads well for the use case.
# Accept any number of positional arguments
def sum_all(*prices):
print(prices) # (100, 200, 300)
print(type(prices)) # <class 'tuple'>
return sum(prices)
print(sum_all(100, 200, 300)) # 600
print(sum_all(500)) # 500 (one is fine)
print(sum_all()) # 0 (zero gives an empty tuple)
Combining regular arguments with *args
Put regular arguments before *args and you can mix a fixed leading argument with a variable-length tail. Writing log(level, *messages) means the first value is bound to level and everything else is collected into a tuple called messages. The order is regular arguments → *args.
def log(level, *messages):
for m in messages:
print(f"[{level}] {m}")
log("INFO", "Started up", "Connected")
# [INFO] Started up
# [INFO] Connected
Variable-length keyword arguments **kwargs — collect into a dict
Two asterisks (`kwargs) lets the function accept any keyword arguments in name=value form. Inside the function, those arguments arrive as a dict, which makes this perfect when the keys aren't known ahead of time**.
The conventional name is kwargs (short for keyword arguments).
Calling show(name="Alice", age=30) gives the receiver the dict {"name": "Alice", "age": 30}. Not having to declare the keys upfront is the whole point.
def describe_user(**info):
print(info) # {'name': 'Alice', 'age': 30, 'city': 'New York'}
print(type(info)) # <class 'dict'>
for key, value in info.items():
print(f"{key}: {value}")
describe_user(name="Alice", age=30, city="New York")
Combining regular arguments with **kwargs
Put a regular argument before **kwargs and you get **a fixed required value** plus **flexible optional fields**. Defining def save_user(name, **info): means name is always required, while extra fields like age=30 or city="New York" get collected into the info dict.
def save_user(name, **info):
print(f"name: {name}")
for key, value in info.items():
print(f"{key}: {value}")
save_user("Alice", age=30, city="New York")
# name: Alice
# age: 30
# city: New York
Using regular arguments, *args, and **kwargs together
All three kinds of arguments can coexist in one function. The order is fixed: regular arguments → *args → **kwargs. Writing def register(user_id, *tags, **meta): binds the first value to user_id, collects any further positional arguments into the tuple tags, and collects every key=value argument into the dict meta.
def register(user_id, *tags, **meta):
print(f"id: {user_id}")
print(f"tags: {tags}")
print(f"meta: {meta}")
register(1001, "admin", "beta", email="alice@example.com", active=True)
# id: 1001
# tags: ('admin', 'beta')
# meta: {'email': 'alice@example.com', 'active': True}
Caller-side unpacking — *list / **dict
When you pass an existing list or dict to a function, whether or not you put * or ** in front changes the result dramatically.
If you pass it as-is without * or **, the list or dict is treated **as a single value that doesn't get unpacked**. Receive it through *args and you'll end up with a tuple containing one list (e.g. ([1, 2, 3],)`) — that's the catch.
With `list / dict on the call side, the contents are spread out one by one, so *args / **kwargs see the expected (1, 2, 3) tuple or {"name": "Alice"} dict.
def sum_all(*prices):
return sum(prices)
prices = [120, 280, 550]
# X: passed as a single list
# print(sum_all(prices)) # error (sum tries to add the list itself to numbers)
# O: spread it out with `*`
print(sum_all(*prices)) # 950 — same as sum_all(120, 280, 550)
# Same idea with dicts
def announce(name, city):
print(f"{name} from {city}")
user = {"name": "Bob", "city": "Toronto"}
announce(**user) # same as announce(name="Bob", city="Toronto")
Unpacking shines when passing existing data
When you already have data as a list or dict, unpacking gets it into a function in a single line.
For example, the dict user = {"name": "Bob", "city": "Toronto"} becomes announce(**user) — that's the same as writing announce(name="Bob", city="Toronto"). You don't have to pull each key out by hand.
Multiple return values — return as a tuple, unpack on receipt
return accepts multiple comma-separated values, and the caller gets a tuple of those values. Receive them with a, b = function() to unpack the tuple into multiple variables in one go.
This is handy whenever you want to return multiple values from a function (min and max, username and role, success flag and message, and so on).
# Return multiple values at once
def get_person():
return "Alice", 30, "New York"
# Receive as a tuple
result = get_person()
print(result) # ('Alice', 30, 'New York')
print(type(result)) # <class 'tuple'>
# Or unpack into individual variables
name, age, city = get_person()
print(name, age, city) # Alice 30 New York
# Practical example: return min and max together
def get_min_max(values):
return min(values), max(values)
lowest, highest = get_min_max([120, 500, 300, 180])
print(f"min {lowest} / max {highest}")
Use *rest to grab the leftovers
Given a list of test scores like return 90, 85, 78, 92, 88, write first, *middle, last = ... and you'll get first=90, last=88, middle=[85, 78, 92] — the first and last values, with the rest collected into a list. It's the right tool when you want to keep just the head and tail and roll up everything in between.
Knowledge Check
Answer each question one by one.
Q2What's the type of kwargs received by def f(**kwargs):?
Q3Given nums = [1, 2, 3], which call passes its contents as three positional arguments to f(a, b, c)?