Skip to main content

*args and **kwargs: Accept Variable Arguments

*args and **kwargs allow you to write functions that accept a variable number of arguments. This flexibility is essential for building robust libraries, decorators, and frameworks that don't force callers to predict argument counts in advance. By using these patterns, you unlock one of Python's most powerful capabilities: universal function wrappers.

Capturing Positional Arguments with *args

The *args syntax (the single asterisk is critical; args is just convention) collects extra positional arguments into a tuple. Inside the function, you can iterate over this tuple like any other.

How do you use *args to accept a variable number of positional arguments?

def sum_all(*numbers):
"""
Calculates the sum of all positional arguments passed to it.
'numbers' will be a tuple.
"""
print(f"Arguments received as a tuple: {numbers}")
total = 0
for num in numbers:
total += num
return total

# Call the function with different numbers of arguments
print(f"Sum 1: {sum_all(1, 2)}")
print(f"Sum 2: {sum_all(10, 20, 30, 40, 50)}")
print(f"Sum 3: {sum_all()}")

Output:

Arguments received as a tuple: (1, 2)
Sum 1: 3
Arguments received as a tuple: (10, 20, 30, 40, 50)
Sum 2: 150
Arguments received as a tuple: ()
Sum 3: 0

Code breakdown:

  • def sum_all(*numbers): — The * makes numbers a tuple containing all positional arguments passed.
  • Inside the function, numbers behaves like any tuple: you can iterate, index, slice, or pass it to other functions.
  • Calling sum_all() with zero arguments is valid; numbers becomes an empty tuple ().

The variable name doesn't have to be args — it's just convention. What matters is the * prefix.


Capturing Keyword Arguments with **kwargs

The **kwargs syntax (double asterisk) collects extra keyword (named) arguments into a dictionary. This is invaluable for functions that accept optional configuration parameters or arbitrary attributes.

How do you use **kwargs to accept a variable number of keyword arguments?

def display_user_profile(**user_info):
"""
Displays user information passed as keyword arguments.
'user_info' will be a dictionary.
"""
print(f"Arguments received as a dictionary: {user_info}")
if 'name' not in user_info:
print("Error: 'name' is a required field.")
return

print("\n--- User Profile ---")
for key, value in user_info.items():
print(f"{key.title()}: {value}")
print("--------------------")


# Call the function with different keyword arguments
display_user_profile(name="Brenda", age=42, city="Metropolis")
display_user_profile(name="Carlos", occupation="Engineer")
display_user_profile(age=30) # Missing the 'name' field

Output:

Arguments received as a dictionary: {'name': 'Brenda', 'age': 42, 'city': 'Metropolis'}

--- User Profile ---
Name: Brenda
Age: 42
City: Metropolis
--------------------
Arguments received as a dictionary: {'name': 'Carlos', 'occupation': 'Engineer'}

--- User Profile ---
Name: Carlos
Occupation: Engineer
--------------------
Arguments received as a dictionary: {'age': 30}
Error: 'name' is a required field.

Code breakdown:

  • def display_user_profile(**user_info): — The ** makes user_info a dictionary containing all keyword arguments.
  • Inside the function, access keys like a normal dictionary: user_info['name'] or 'name' in user_info.
  • You can validate required fields by checking dictionary membership before processing.

Combining Regular Args, *args, and **kwargs

When you mix standard positional arguments, *args, and **kwargs, they must appear in a specific order in the function signature: standard arguments first, then *args, then **kwargs.

How do you use standard arguments, *args, and **kwargs together?

def process_order(order_id, *items, **customer_details):
"""Processes a customer order with required and optional details."""
print(f"Processing Order ID: {order_id}")

print("\nItems in Order:")
for item in items:
print(f"- {item}")

print("\nCustomer Details:")
for key, value in customer_details.items():
print(f"- {key.title()}: {value}")

process_order(
101,
"Laptop", "Mouse", "Keyboard",
name="Diana Prince",
shipping_address="25 Paradise Island"
)

Output:

Processing Order ID: 101

Items in Order:
- Laptop
- Mouse
- Keyboard

Customer Details:
- Name: Diana Prince
- Shipping_Address: 25 Paradise Island

Order matters: If you reverse the order (e.g., **kwargs before *args), Python raises a SyntaxError. The function signature is the contract between the function and its callers.


Unpacking: Using * and ** to Pass Arguments

The reverse operation is equally powerful. If you have a list or dictionary and need to pass its contents as individual arguments to a function, use unpacking:

  • * unpacks a list or tuple into positional arguments.
  • ** unpacks a dictionary into keyword arguments.

How do you unpack a list or dictionary when calling a function?

def create_point(x, y, z):
"""Creates a 3D point."""
print(f"Point created at (x={x}, y={y}, z={z})")

# Unpacking a list/tuple with *
coords_list = [10, 20, 30]
create_point(*coords_list)

# Unpacking a dictionary with **
coords_dict = {'x': 5, 'y': 15, 'z': 25}
create_point(**coords_dict)

Output:

Point created at (x=10, y=20, z=30)
Point created at (x=5, y=15, z=25)

When this is useful: You have data in a list or dictionary (perhaps from a database or JSON file), and you need to pass it to a function that expects individual arguments. Unpacking lets you do this without manually extracting each value.


Key Takeaways

  • *args for positional: Captures unlimited positional arguments into a tuple.
  • **kwargs for keyword: Captures unlimited keyword arguments into a dictionary.
  • Order in function signature: Standard arguments first, then *args, then **kwargs.
  • Unpacking reverses the operation: Use * and ** when calling a function to pass list/dictionary contents as arguments.
  • Names are conventions: You can name them *numbers and **options instead; what matters is the * and ** symbols.

Frequently Asked Questions

Why is *args a tuple and not a list?

Tuples are immutable (cannot be modified), so Python uses them to prevent accidental modification of collected arguments. Since the tuple is created from the function call itself, using an immutable type is a design choice emphasizing that these arguments should be treated as read-only data.

Can you use *args or **kwargs if the function takes no other arguments?

Absolutely. def my_func(*args): and def my_func(**kwargs): are valid. You don't need standard parameters.

What happens if you pass a list to a function expecting *args?

If you pass a list directly (without unpacking), it becomes a single argument. For example, sum_all([1, 2, 3]) results in numbers = ([1, 2, 3],) — a tuple containing one list. To unpack, use sum_all(*[1, 2, 3]), which results in numbers = (1, 2, 3).

Can keyword arguments be passed after positional arguments?

Yes, keyword arguments can appear anywhere after all positional arguments in a function call. However, in the function signature, *args must come before **kwargs. Once you use *args, any subsequent parameters become keyword-only.

How do you create a function that requires at least one argument with *args?

Use a standard parameter followed by *args: def func(first_arg, *remaining_args):. This ensures at least one argument is always provided. first_arg is required; remaining_args collects any extras.


Further Reading