Learn by reading through in order

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.

Collecting positional arguments with *args
caller sidesum_all(100, 200, 300)definitiondef sum_all(*prices):inside the functionprices = (100, 200, 300)passcollect
# 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.

Regular arguments combined with *args
log("INFO", "Started up", "Connected")def log(level, *messages):level = "INFO"messages =("Started up", "Connected")passfirst goes to regular argrest become a tuple
def log(level, *messages):
    for m in messages:
        print(f"[{level}] {m}")

log("INFO", "Started up", "Connected")
# [INFO] Started up
# [INFO] Connected

Build a function that accepts any number of product prices and returns their total.

① Define def calc_total(*prices): and return sum(prices).

② Print the result of calc_total(300, 500) and calc_total(120, 280, 550, 400). Confirm that the same function works regardless of how many arguments you pass.

(Once it runs correctly, the explanation appears.)

Python Editor

Run code to see output

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).

Collecting keyword arguments with **kwargs
caller sideshow(name="Alice", age=30)definitiondef show(**info):inside the functioninfo = {"name": "Alice", "age": 30}type: dictpasscollect

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.

Regular arguments combined with **kwargs
save_user("Alice", age=30, city="New York")def save_user(name, **info):name = "Alice"info ={"age": 30, "city": "New York"}passfirst goes to regular argrest become a 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

Write a function that takes user info as keyword arguments and prints each entry on its own line.

① Define def show_profile(**info): and loop with for key, value in info.items(): printing f"{key}: {value}".

② Call it with show_profile(name="Alice", age=20, city="London").

Python Editor

Run code to see output

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}

Write a function that uses regular arguments, *args, and **kwargs all at once.

① Define def register(user_id, *tags, **meta): and print three lines: f"id: {user_id}", f"tags: {tags}", and f"meta: {meta}".

② Call register(1001, "admin", "beta", email="alice@example.com", active=True) and watch how each argument lands in its slot.

Python Editor

Run code to see output

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.

Whether you write * or not changes what *args contains
lst = [1, 2, 3]f(lst)(no *)args =([1, 2, 3],)(one list)lst = [1, 2, 3]f(*lst)(with *)args =(1, 2, 3)(unpacked)stays as onespread out
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.

Take the existing list cart_prices = [480, 1200, 320, 980] and spread it out into the calc_total function from earlier.

① Redefine def calc_total(*prices): returning sum(prices).

② Declare cart_prices = [480, 1200, 320, 980] and print() the result of calc_total(*cart_prices).

③ Try print(calc_total(cart_prices)) without the asterisk. You'll get an error — read it and confirm that the list itself ends up as the first argument.

Python Editor

Run code to see output

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).

Multiple return values come back as a tuple
def get_person():return name, age, city(name, age, city)(tuple)result = get_person()# result stays a tuplename, age, city= get_person()spreads intoname, age, cityactual valueunpack
# 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.

Write a function that returns both the lowest and highest price from a list of products.

① Define def get_price_range(prices): and return min(prices), max(prices) to send back two values.

② Prepare product_prices = [480, 1200, 320, 980, 650] and unpack the result with lowest, highest = get_price_range(product_prices).

③ Print f"Lowest: {lowest}" and f"Highest: {highest}".

Python Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1What does this code print?
def f(*args):
return sum(args)

print(f(1, 2, 3, 4))

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)?