Question
Python *args and **kwargs Explained in Function Parameters
Question
In Python, what do *args and **kwargs mean when they appear in function definitions like these?
def foo(x, y, *args):
pass
def bar(x, y, **kwargs):
pass
I want to understand what the single asterisk (*) and double asterisk (**) do for parameters in a function definition, and how they affect the values a function can accept.
Short Answer
By the end of this page, you will understand how *args collects extra positional arguments, how **kwargs collects extra keyword arguments, when to use each one, and how they fit into real Python function design.
Concept
In Python function definitions, *args and **kwargs let a function accept a flexible number of arguments.
*argscollects extra positional arguments into a tuple.**kwargscollects extra keyword arguments into a dictionary.
This is useful when you do not know in advance exactly how many arguments will be passed to a function, or when you want to forward arguments to another function.
For example:
def foo(x, y, *args):
print(x)
print(y)
print(args)
foo(1, 2, 3, 4, 5)
Output:
1
2
(3, 4, 5)
Here:
xgets1ygets2argsgets the remaining positional values as a tuple:(3, 4, 5)
Now with **kwargs:
def bar(x, y, **kwargs):
print(x)
print(y)
print(kwargs)
bar(1, 2, color="blue", size="large")
Output:
1
2
{'color': 'blue', 'size': 'large'}
Here:
xgets1ygets2kwargsgets all extra named arguments as a dictionary
The names args and kwargs are conventional, but not required. These are valid too:
def example(*numbers, **options):
print(numbers)
print(options)
What matters is the * and **, not the variable names.
This concept matters because many real Python APIs use flexible function signatures. You will see it in libraries, decorators, wrappers, logging helpers, configuration functions, and framework code.
Mental Model
Think of a function like a bag-check desk at an airport.
- Regular parameters like
xandyare for bags with fixed labels. *argsis a large bin for any extra unlabeled bags.**kwargsis a separate bin for extra bags with labels attached.
So if a function knows it always needs two required values, but might receive more:
- the extra unnamed values go into
*args - the extra named values go into
**kwargs
Another way to think about it:
*args= "collect leftover positional items"**kwargs= "collect leftover named items"
This makes functions more flexible without forcing you to define every possible parameter up front.
Syntax and Examples
Basic syntax
def func(required1, required2, *args, **kwargs):
print(required1)
print(required2)
print(args)
print(kwargs)
Example with *args
def add_numbers(*args):
return sum(args)
print(add_numbers(1, 2, 3))
print(add_numbers(10, 20, 30, 40))
Output:
6
100
Explanation:
- All positional arguments are collected into the tuple
args sum(args)works becauseargsbehaves like a normal tuple of numbers
Example with **kwargs
def print_user(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_user(name="Ava", role="admin", active=True)
Possible output:
name: Ava
role: admin
active: True
Explanation:
- Every named argument is collected into the dictionary
kwargs - You can loop through it like any other dictionary
Example using both
def demo(x, *args, **kwargs):
print("x =", x)
print("args =", args)
print("kwargs =", kwargs)
demo(5, 10, 15, color="red", enabled=True)
Output:
x = 5
args = (10, 15)
kwargs = {'color': 'red', 'enabled': True}
Important note
*args and **kwargs are common names, but you could write:
def demo(*values, **options):
print(values)
print(options)
This works exactly the same way.
Step by Step Execution
Consider this code:
def show(a, b, *args, **kwargs):
print("a:", a)
print("b:", b)
print("args:", args)
print("kwargs:", kwargs)
show(1, 2, 3, 4, color="blue", size="M")
Step 1: Match required positional parameters
Python assigns the first positional argument to a:
a = 1
Then the second positional argument to b:
b = 2
Step 2: Collect remaining positional arguments into args
The remaining positional arguments are 3 and 4.
So:
args = (3, 4)
This is a tuple.
Step 3: Collect named arguments into kwargs
The keyword arguments are:
color="blue"size="M"
So:
kwargs = {'color': 'blue', 'size': 'M'}
This is a dictionary.
Step 4: Print the values
The function prints:
a: 1
b: 2
args: (3, 4)
kwargs: {'color': 'blue', 'size': 'M'}
Key takeaway
Python fills normal parameters first, then gathers extras:
- required and named parameters
- extra positional arguments into
*args - extra keyword arguments into
**kwargs
Real World Use Cases
1. Wrapper functions
You may write a function that adds logging before calling another function.
def logged_call(func, *args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
This lets the wrapper accept any arguments the wrapped function needs.
2. Framework and library APIs
Many frameworks accept optional settings through keyword arguments.
def connect(host, port, **options):
print(host, port, options)
This allows new options to be added later without changing every call.
3. Building utility functions
A function might need to accept any number of values.
def average(*numbers):
return sum(numbers) / len(numbers)
4. Passing configuration data
Keyword arguments are useful for configuration-like input.
def create_user(username, **profile):
return {
"username": username,
"profile": profile
}
5. Decorators
Decorators often need to support functions with different signatures.
def debug(func):
def wrapper(*args, **kwargs):
print("args:", args)
print("kwargs:", kwargs)
return func(*args, **kwargs)
return wrapper
Real Codebase Usage
In real projects, developers use *args and **kwargs carefully, not everywhere.
Common patterns
Forwarding arguments
A function may pass received arguments to another function.
def save_with_log(*args, **kwargs):
print("Saving data...")
return save(*args, **kwargs)
This is common in wrappers, adapters, and decorators.
Optional extension points
Libraries sometimes use **kwargs to allow extra options without breaking older code.
def send_email(to, subject, **options):
timeout = options.get("timeout", 30)
retry = options.get("retry", False)
Validation
Good code often validates values inside kwargs.
def create_product(name, **kwargs):
allowed = {"price", "stock", "category"}
unknown = set(kwargs) - allowed
if unknown:
raise TypeError(f"Unexpected options: {unknown}")
Guard clauses
Functions may check for missing or invalid flexible inputs early.
def process_items(*args):
if not args:
raise ValueError("At least one item is required")
return [str(item).upper() for item in args]
Compatibility layers
When refactoring a large codebase, **kwargs can help temporarily support old and new call styles while the code is being migrated.
Best practice in real code
- Use flexible parameters when they truly add value
- Prefer explicit parameter names when possible
- Document what extra values are expected
- Validate supported keys if using
**kwargs - Avoid hiding too much behavior behind vague argument collection
Common Mistakes
1. Thinking args and kwargs are special names
They are just conventional names.
def test(*values, **options):
print(values)
print(options)
The special part is the * and **.
2. Forgetting the data types they create
*argsbecomes a tuple**kwargsbecomes a dictionary
Broken expectation:
def bad(*args):
args.append(5) # tuples do not have append
Fix:
def good(*args):
values = list(args)
values.append(5)
return values
3. Using **kwargs without checking valid keys
Broken example:
def create_user(**kwargs):
return kwargs["name"]
This crashes if name is missing.
Safer version:
def create_user(**kwargs):
if "name" not in kwargs:
raise ValueError("name is required")
return kwargs["name"]
4. Overusing flexible arguments
This can make functions hard to understand.
Less clear:
def configure(*args, **kwargs):
pass
Often better:
def configure(host, port, timeout=30, debug=False):
pass
Use *args and **kwargs when flexibility is actually needed.
5. Mixing up positional and keyword arguments
def example(*args, **kwargs):
print(args)
print(kwargs)
example(1, 2, name="Sam")
Result:
argsis(1, 2)kwargsis{'name': 'Sam'}
6. Assuming **kwargs accepts any key format in code syntax
In a normal function call, keyword argument names must be valid identifiers.
This is valid:
print_user(name="Ava")
But this is not valid syntax:
# print_user(first-name="Ava")
If needed, dictionary unpacking at call time can be used in other contexts, but inside the function kwargs is still just a dictionary.
Comparisons
| Concept | What it collects | Resulting type | Example |
|---|---|---|---|
| Regular parameter | One specific argument | Any value | def f(x): |
*args | Extra positional arguments | tuple | def f(*args): |
**kwargs | Extra keyword arguments | dict | def f(**kwargs): |
*args vs **kwargs
| Feature | *args | **kwargs |
|---|---|---|
| Accepts | Positional arguments | Keyword arguments |
| Stored as | Tuple | Dictionary |
| Example call | f(1, 2, 3) | f(name="Ava", age=20) |
| Good for | Variable-length lists of values | Optional named settings |
Explicit parameters vs flexible parameters
| Style | Example | Best when |
|---|---|---|
| Explicit | def connect(host, port): | Inputs are known and fixed |
| Flexible positional | def add(*numbers): | Number of items varies |
| Flexible keyword | def build(**options): | Optional named settings vary |
| Mixed | def run(task, *args, **kwargs): | A wrapper or forwarding function is needed |
Related syntax in calls
In definitions:
*argsmeans collect extra positional arguments**kwargsmeans collect extra keyword arguments
In function calls, * and ** do the opposite job:
*sequenceunpacks positional values**mappingunpacks keyword values
That is a related but separate concept.
Cheat Sheet
# Collect extra positional arguments
def func(*args):
print(args) # tuple
# Collect extra keyword arguments
def func(**kwargs):
print(kwargs) # dict
# Use both
def func(a, b, *args, **kwargs):
pass
Rules
*argscollects leftover positional arguments**kwargscollects leftover keyword argumentsargsandkwargsare naming conventions, not keywords*argsis a tuple**kwargsis a dictionary- Use them when the number or names of inputs may vary
Example
def demo(x, *args, **kwargs):
print(x)
print(args)
print(kwargs)
demo(1, 2, 3, color="red")
Result:
1
(2, 3)
{'color': 'red'}
Good uses
- Wrappers
- Decorators
- Forwarding arguments
- Variable-length input
- Optional configuration
Watch out for
- Making function APIs too vague
- Forgetting
argsis a tuple - Forgetting
kwargsis a dict - Not validating supported keyword options
FAQ
What does *args mean in Python?
*args means a function can accept any number of extra positional arguments. Inside the function, they are stored as a tuple.
What does **kwargs mean in Python?
**kwargs means a function can accept any number of extra keyword arguments. Inside the function, they are stored as a dictionary.
Are args and kwargs special Python keywords?
No. They are just common names. The special meaning comes from * and ** in the function definition.
Can I use both *args and **kwargs in the same function?
Yes. This is very common in wrappers and decorators.
When should I use *args?
Use *args when a function should accept a variable number of positional values, such as many numbers to sum.
When should I use **kwargs?
Use **kwargs when a function should accept optional named settings or when forwarding options to another function.
Is *args always a tuple and **kwargs always a dictionary?
Yes. In a function definition, *args becomes a tuple and **kwargs becomes a dictionary.
Should I always use *args and **kwargs for flexibility?
No. Prefer explicit parameters when the expected inputs are known. Use flexible arguments only when they make the function genuinely more useful.
Mini Project
Description
Build a small Python utility function that records events with a required event name, any number of extra positional tags, and any number of keyword details. This demonstrates how *args and **kwargs can be used together in a realistic logging-style function.
Goal
Create a function that accepts fixed parameters plus flexible extra data, then prints the collected information in a clear format.
Requirements
- Create a function named
log_eventthat requires one parameter calledevent_name - Allow the function to accept any number of extra positional arguments as tags
- Allow the function to accept any number of keyword arguments as details
- Print the event name, the tags, and the details in a readable way
- Call the function with at least two different examples
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.