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. args and kwargs are conventional variable names, not Python keywords. You may choose more descriptive names:
def total(*numbers):
return sum(numbers)
def create_user(**details):
return details
This feature matters because it helps you write reusable functions, wrappers, configuration APIs, logging helpers, and functions that pass arguments on to another function.
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(f"{key}: {value}")
describe_product(name="Keyboard", price=49.99, available=True)
Output:
name: Keyboard
price: 49.99
available: True
You can combine fixed parameters with flexible ones:
def announce(title, *names, **options):
separator = options.get("separator", ", ")
print(f"{title}: {separator.join(names)}")
announce("Attendees", "Ava", "Noah", "Mina", separator=" | ")
# Attendees: Ava | Noah | Mina
The usual parameter order is:
def function_name(required, default_value="x", *args, keyword_only="y", **kwargs):
pass
After *args, ordinary named parameters become keyword-only. keyword_only must be passed by name.
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
label="Final score"is collected intosettings:settings == {"label": "Final score"} -
sum(scores)is240, andlen(scores)is3, soaverageis80.0. -
settings.get("label", "Average")finds the supplied label. -
The function returns:
Math - Final score: 80.0
The same symbols also unpack collections at the call site:
def point(x, y):
return f"({x}, {y})"
coordinates = [3, 7]
print(point(*coordinates))
options = {"sep": " - ", "end": "!\n"}
print("Ready", "Set", **options)
*coordinates passes the list items as positional arguments. **options passes dictionary entries as keyword arguments.
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.
def timed_call(function, *args, **kwargs): return function(*args, **kwargs) -
Combining settings: Build a dictionary of defaults, then override selected values for one call.
defaults = {"timeout": 10, "retries": 2} connect("api.example.com", **defaults)
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 with_logging(function):
def wrapper(*args, **kwargs):
print(f"Calling {function.__name__}")
return function(*args, **kwargs)
return wrapper
@with_logging
def add(left, right):
return left + right
print(add(2, 3))
Merging configuration dictionaries
Use dictionary unpacking to create a new configuration without mutating the defaults:
DEFAULTS = {"timeout": 10, "retries": 3}
user_options = {"timeout": 30}
config = {**DEFAULTS, **user_options}
# {'timeout': 30, 'retries': 3}
For stable public APIs, explicit keyword-only parameters are often clearer than unrestricted **kwargs:
def fetch(url, *, timeout=10, retries=3):
pass
This documents which options are supported and lets Python reject misspelled names.
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:
add(*numbers) # 5
Using ** with non-string dictionary keys
Keyword argument names must be strings:
settings = {1: "fast"}
# print(**settings) # TypeError
Use a string key instead:
settings = {"end": "!\n"}
print("Done", **settings)
Duplicating an argument
A parameter cannot receive a value twice:
def greet(name):
print(name)
values = {"name": "Mina"}
# greet("Ava", **values) # TypeError: multiple values for argument 'name'
Pass it only once.
Using **kwargs when explicit parameters are clearer
Avoid hiding a small, known set of options in a dictionary:
# Less clear for a fixed API
def resize(image, **options):
pass
# Clearer
def resize(image, width, height, keep_ratio=True):
pass
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:
# Fixed: exactly two values
def add(left, right):
return left + right
# Flexible: zero or more values
def add_all(*numbers):
return sum(numbers)
Use fixed parameters when the required inputs are known. Use *args when the amount of positional data genuinely varies.
Compare **kwargs with explicit keyword-only parameters:
# Accepts any keyword name unless you validate it
def request(url, **options):
pass
# Documents and restricts supported options
def request(url, *, timeout=10, verify_tls=True):
pass
For most application functions, explicit keyword-only parameters are easier to discover and maintain. **kwargs is especially valuable for forwarding arguments or handling open-ended metadata.
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. - Prefer explicit parameters when the accepted inputs are known and limited.
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?
At a call site, * unpacks an iterable so each item becomes a separate positional argument.
print(*[1, 2, 3])
Why use ** when calling a Python function?
It unpacks a dictionary so each key-value pair becomes a keyword argument.
print("Hello", **{"end": "!\n"})
Can **kwargs accept any dictionary?
Its keys must be strings suitable for keyword argument names. For example, { "timeout": 5 } works, while { 1: "x" } does not.
Should every function use **kwargs for options?
No. Prefer clear named parameters for known options. Use **kwargs when options are truly open-ended or must be forwarded to another 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.