*args and **kwargs¶
In this tutorial, you will learn how to write functions that accept any number of positional and keyword arguments using *args and **kwargs, and how to unpack arguments when calling functions.
Time commitment: 15–20 minutes
Prerequisites:
- Tutorials 01–05 (Defining functions, Lambda expressions, Type hints, Docstrings, and Scope and closures)
- Familiarity with tuples and dictionaries
Learning objectives¶
By the end of this tutorial, you will be able to:
- Use
*argsto accept variable numbers of positional arguments - Use
**kwargsto accept variable numbers of keyword arguments - Combine
*argsand**kwargsin a single function - Understand argument unpacking with
*and**
Why variable arguments?¶
Sometimes you do not know in advance how many arguments a function will receive. For example, imagine a function that calculates the average of test scores. One student might have three scores, whilst another might have seven.
You could accept a list, but Python offers a more elegant solution: *args and **kwargs. These let you define functions that accept any number of arguments without the caller needing to create a list or dictionary first.
*args: variable positional arguments¶
When you prefix a parameter with an asterisk (*), it collects any extra positional arguments into a tuple. By convention, this parameter is named args, but you can use any valid name:
def show_args(*args):
print(f"Type: {type(args)}")
print(f"Values: {args}")
show_args(1, 2, 3)
show_args("hello", "world")
show_args()
Notice that args is always a tuple, even when no extra arguments are provided (in which case it is an empty tuple).
Using *args in practice¶
Let us write some useful functions that take advantage of *args. First, a function that sums any number of values:
def total(*values):
"""Return the sum of all given values."""
result = 0
for value in values:
result += value
return result
print(total(1, 2, 3))
print(total(10, 20, 30, 40))
print(total(5))
Here is another example — a function that joins strings with a custom separator:
def join_words(separator, *words):
"""Join all words with the given separator."""
return separator.join(words)
print(join_words(", ", "Python", "Java", "JavaScript"))
print(join_words(" - ", "Monday", "Tuesday", "Wednesday"))
Notice that separator is a regular parameter that comes before *words. You can mix regular parameters with *args, but *args must come after all regular positional parameters.
**kwargs: variable keyword arguments¶
The double asterisk (**) prefix collects extra keyword arguments into a dictionary. By convention, this parameter is named kwargs:
def show_kwargs(**kwargs):
print(f"Type: {type(kwargs)}")
for key, value in kwargs.items():
print(f" {key} = {value}")
show_kwargs(name="Alice", age=30, city="London")
The keyword arguments are collected into a dictionary where the argument names become the keys.
Using **kwargs in practice¶
Let us write a function that builds a formatted profile from keyword arguments:
def build_profile(first_name, last_name, **details):
"""Build a dictionary representing a user profile."""
profile = {
"first_name": first_name,
"last_name": last_name,
}
profile.update(details)
return profile
user = build_profile("Ada", "Lovelace", field="Mathematics", born=1815)
print(user)
The first_name and last_name parameters are required, whilst any additional keyword arguments are captured in details and merged into the profile dictionary.
Combining *args and **kwargs¶
You can use both *args and **kwargs in the same function. The required order for parameters is:
- Regular positional parameters
*args- Keyword parameters (with or without defaults)
**kwargs
def log_event(event_type, *tags, severity="info", **metadata):
"""Log an event with optional tags and metadata."""
print(f"[{severity.upper()}] {event_type}")
if tags:
print(f" Tags: {', '.join(tags)}")
for key, value in metadata.items():
print(f" {key}: {value}")
log_event("User login", "auth", "security", user="alice", ip="192.168.1.1")
print()
log_event("Server start", severity="warning", port=8080)
In this example:
event_typeis a regular positional parameter*tagscaptures extra positional arguments ("auth","security")severityis a keyword parameter with a default value**metadatacaptures extra keyword arguments (user,ip,port)
Argument unpacking¶
The * and ** operators also work in the opposite direction — you can use them to unpack a sequence or dictionary into function arguments.
def add(a, b, c):
return a + b + c
# Unpack a list into positional arguments
numbers = [10, 20, 30]
print(add(*numbers))
# Unpack a dictionary into keyword arguments
values = {"a": 100, "b": 200, "c": 300}
print(add(**values))
Unpacking is especially useful when you have data stored in a collection and want to pass it to a function that expects separate arguments:
def format_date(day, month, year):
"""Format a date in DD/MM/YYYY style."""
return f"{day:02d}/{month:02d}/{year}"
date_parts = {"day": 25, "month": 12, "year": 2025}
print(format_date(**date_parts))
Keyword-only arguments¶
You can use a bare * separator in the parameter list to force all subsequent parameters to be passed as keyword arguments. This makes function calls more readable and less error-prone:
def create_user(name, *, email, role="member"):
"""Create a user. email and role must be passed as keyword arguments."""
return {"name": name, "email": email, "role": role}
# This works
user = create_user("Alice", email="alice@example.com", role="admin")
print(user)
# This would raise a TypeError:
# create_user("Bob", "bob@example.com") # email must be a keyword argument
Everything after the * separator must be provided as a keyword argument. This prevents confusion when a function has several parameters that could easily be mixed up.
Positional-only arguments¶
Since Python 3.8, you can use / as a separator to make parameters positional-only. Parameters before / cannot be passed as keyword arguments:
def divide(numerator, denominator, /, *, round_to=2):
"""Divide numerator by denominator.
numerator and denominator are positional-only.
round_to is keyword-only.
"""
return round(numerator / denominator, round_to)
# Positional arguments before /, keyword argument after *
print(divide(10, 3))
print(divide(10, 3, round_to=4))
# This would raise a TypeError:
# divide(numerator=10, denominator=3) # must be positional
Combining both separators gives you fine-grained control over how callers use your function:
- Parameters before
/are positional-only - Parameters between
/and*can be either positional or keyword - Parameters after
*are keyword-only
Exercise: statistics and wrapper functions¶
Write a function called calculate_stats that accepts any number of numeric arguments and returns a dictionary with three keys: "sum", "count", and "average".
Then write a wrapper function called print_stats that accepts *args and **kwargs, passes them through to calculate_stats, and prints the results in a formatted way.
For example:
result = calculate_stats(10, 20, 30)
print(result) # Expected: {'sum': 60, 'count': 3, 'average': 20.0}
print_stats(5, 15, 25, 35)
# Expected output:
# Sum: 80
# Count: 4
# Average: 20.0
Try writing both functions in the cell below.
# Write your calculate_stats and print_stats functions here
# Test calculate_stats
# result = calculate_stats(10, 20, 30)
# print(result)
# Test print_stats
# print_stats(5, 15, 25, 35)
Solution¶
Here is one way to write the functions:
def calculate_stats(*numbers):
"""Calculate sum, count, and average for the given numbers."""
total = sum(numbers)
count = len(numbers)
average = total / count if count > 0 else 0
return {"sum": total, "count": count, "average": average}
def print_stats(*args, **kwargs):
"""Print statistics for the given numbers."""
stats = calculate_stats(*args, **kwargs)
print(f"Sum: {stats['sum']}")
print(f"Count: {stats['count']}")
print(f"Average: {stats['average']}")
result = calculate_stats(10, 20, 30)
print(result)
print()
print_stats(5, 15, 25, 35)
The print_stats function is a wrapper — it accepts any arguments, passes them through to calculate_stats using unpacking, and then formats the results. This pattern is very common in Python and is essential for writing decorators, which you will learn about in the next tutorial.
Summary¶
In this tutorial, you learned how to:
- Use
*argsto collect variable numbers of positional arguments into a tuple - Use
**kwargsto collect variable numbers of keyword arguments into a dictionary - Combine
*argsand**kwargsin the correct order with regular parameters - Unpack sequences with
*and dictionaries with**when calling functions - Define keyword-only arguments using a
*separator - Define positional-only arguments using a
/separator
What is next¶
In the next tutorial, you will explore decorators — functions that modify other functions. The *args and **kwargs pattern you learned here is the foundation for writing decorators.