Question
How do *args and **kwargs work in Python function definitions and function calls? I understand that *args collects extra positional arguments and **kwargs collects extra keyword arguments, but I do not understand when this flexibility is useful. Can you show a simple practical example? Also, are the names args and kwargs required, or are they only conventions?
Short Answer
*args and **kwargs let a Python function accept a variable number of arguments. You will learn how Python collects extra positional values into a tuple, collects extra named values into a dictionary, and unpacks existing lists, tuples, and dictionaries when calling functions.
Concept
A function usually has a fixed interface:
def greet(name, message):
return f"{message}, {name}!"
It requires exactly two arguments. This is ideal when every call should have the same shape.
Sometimes a function cannot know in advance how many values a caller will provide. For example, a total() function may need to add two numbers today and ten numbers tomorrow. Python handles this with variable-length arguments:
*argscollects extra positional arguments into a tuple.**kwargscollects extra keyword arguments into a dictionary.
def show_values(*args, **kwargs):
print(args) # tuple
print(kwargs) # dictionary
show_values("red", "blue", size="large", in_stock=True)
# ('red', 'blue')
# {'size': 'large', 'in_stock': True}
The * and are the meaningful syntax. and are conventional variable names, not Python keywords. You may choose more descriptive names:
Mental Model
Think of a function as a person accepting deliveries.
- Normal parameters are labelled delivery slots:
nameandageeach expect one specific package. *argsis a box for any extra unlabelled packages that arrive in order.**kwargsis a filing cabinet for any extra labelled packages, such ascolor="blue"orpriority=True.
When calling a function, the direction can reverse:
*a_listopens a sequence and hands over its items one by one.**a_dictopens a dictionary and hands over its key-value pairs as named arguments.
Syntax and Examples
Define a function with *name to collect extra positional arguments:
def total(*numbers):
return sum(numbers)
print(total(4, 8))
print(total(1, 2, 3, 4, 5))
print(total())
Output:
12
15
0
Inside total, numbers is a tuple. Calling total(1, 2, 3) makes numbers equal to (1, 2, 3).
Define a function with **name to collect extra keyword arguments:
def describe_product(**details):
for key, value in details.items():
print()
describe_product(name=, price=, available=)
Step by Step Execution
Consider this function call:
def make_report(category, *scores, **settings):
average = sum(scores) / len(scores)
label = settings.get("label", "Average")
return f"{category} - {label}: {average:.1f}"
report = make_report("Math", 80, 90, 70, label="Final score")
print(report)
Step by step:
-
Python assigns the first positional value,
"Math", tocategory. -
The remaining positional values,
80,90, and70, are collected intoscores:scores == (80, 90, 70) -
The named argument is collected into :
Real World Use Cases
-
Totals and aggregations: Accept any number of prices, measurements, or scores.
def total_cost(*prices): return sum(prices) -
Logging: Allow optional contextual details without creating a new parameter for every possible field.
def log_event(event, **context): print(event, context) log_event("login", user_id=42, ip_address="203.0.113.5") -
Configuration: Receive optional settings such as
timeout,retries, anddebug.def connect(host, **options): timeout = options.get("timeout", 10) return f"Connecting to {host} with timeout {timeout}" -
Forwarding calls: A wrapper can receive arguments and pass them unchanged to another function.
Real Codebase Usage
In production code, *args and **kwargs are useful, but they should not replace a clear function interface unnecessarily.
Optional named settings with validation
A function may accept selected optional settings and reject unknown ones:
def create_account(username, **options):
allowed = {"is_admin", "send_welcome_email"}
unexpected = set(options) - allowed
if unexpected:
raise TypeError(f"Unknown options: {', '.join(sorted(unexpected))}")
is_admin = options.get("is_admin", False)
send_email = options.get("send_welcome_email", True)
return {"username": username, "is_admin": is_admin, "send_email": send_email}
Wrapper and decorator pattern
Libraries often use *args and **kwargs when writing wrappers because the wrapper should support the original function's argument combinations:
def ():
():
()
function(*args, **kwargs)
wrapper
():
left + right
(add(, ))
Common Mistakes
Thinking *args is a list
It is a tuple:
def example(*args):
print(type(args))
example(1, 2)
# <class 'tuple'>
If you truly need a list, convert it with list(args).
Thinking the names are required
This works, though descriptive names are usually better:
def average(*values):
return sum(values) / len(values)
The star is required; the name is your choice.
Passing a list without unpacking it
This is broken when the function expects separate positional values:
def add(left, right):
return left + right
numbers = [2, 3]
add(numbers) # TypeError: missing 1 required positional argument: 'right'
Unpack the list:
Comparisons
| Feature | Collects in a function definition | Unpacks in a function call | Result or requirement |
|---|---|---|---|
*values | Extra positional arguments | A list, tuple, or other iterable | Collected as a tuple |
**options | Extra keyword arguments | A dictionary with string keys | Collected as a dictionary |
| Normal parameter | One expected argument | One value | Assigned directly |
| Keyword-only parameter | A named option after * | Must be passed as name=value | Clear, restricted option |
Compare fixed and flexible interfaces:
Cheat Sheet
# Collect extra positional arguments into a tuple
def function(*args):
pass
# Collect extra keyword arguments into a dictionary
def function(**kwargs):
pass
# Combine ordinary, variable, and keyword-only parameters
def function(required, *items, option=True, **extra):
pass
# Unpack a sequence into positional arguments
function(*["a", "b"])
# Unpack a dictionary into keyword arguments
function(**{"option": False})
argsandkwargsare conventions; the names may be changed.*argsbecomes a tuple inside the function.**kwargsbecomes a dictionary inside the function.- A dictionary used with
**must have string keys that are valid keyword names. - Use
options.get("key", default)for optional dictionary entries. - Do not pass the same parameter both directly and through
**dictionary.
FAQ
What is the difference between *args and **kwargs in Python?
*args handles extra positional arguments and stores them in a tuple. **kwargs handles extra named arguments and stores them in a dictionary.
Are args and kwargs reserved Python words?
No. Only * and ** have special meaning. Names such as *numbers and **settings are often more descriptive.
Is *args a list or a tuple?
It is a tuple. Tuples preserve order but cannot be modified in place.
Can I use *args and **kwargs in the same function?
Yes.
def example(*items, **options):
print(items, options)
Why use * when calling a Python function?
Mini Project
Description
Build a small order summary function for a checkout script. It accepts any number of item prices and optional named settings, such as a discount percentage, shipping cost, and currency symbol. This models flexible input while still validating the settings your application supports.
Goal
Create a function that calculates and formats an order total from variable prices and optional configuration values.
Requirements
Accept zero or more item prices as positional arguments.
Support discount_percent, shipping, and currency as optional named settings.
Reject unsupported setting names with a clear error.
Apply the discount before adding shipping.
Return a formatted order summary string.
Keep learning
Related questions
@staticmethod vs @classmethod in Python Explained
Learn the difference between @staticmethod and @classmethod in Python with clear examples, use cases, mistakes, and a mini project.
Add Rows to a Pandas DataFrame in Python
Learn how to add rows to a Pandas DataFrame, why repeated row appends are slow, and when to use loc, concat, or record lists.
Call a Function by Name in a Python Module
Learn how to call a function by name in a Python module using strings, getattr, and safe patterns for dynamic function dispatch.