Parameters
What are *args and **kwargs?
*args and **kwargs are special syntax in Python that allow functions to accept a variable number of arguments. The *args collects extra positional arguments into a tuple, while **kwargs collects extra keyword arguments into a dictionary. The names args and kwargs are just conventions, so you can use any names you want, but the * and ** operators are what make them work.
def example(*args, **kwargs):
print("Positional arguments:", args)
print("Keyword arguments:", kwargs)
example(1, 2, 3, name="Alice", age=30)
# Positional arguments: (1, 2, 3)
# Keyword arguments: {'name': 'Alice', 'age': 30}
Naming conventions
While args and kwargs are just variable names, they're widely used conventions. You can use any names:
def example(*numbers, **options):
# numbers is a tuple
# options is a dict
pass
However, using args and kwargs makes your code more readable to other Python developers.
Why this matters
*args and **kwargs give you flexibility when you don't know ahead of time how many arguments a function will receive. They're essential for writing functions that can handle different numbers of inputs, which is common in real-world programming. For example, a function that prints multiple values, a function that processes a variable number of items, or a wrapper function that needs to pass arguments to another function all benefit from using *args and **kwargs. Understanding these concepts opens up powerful patterns in Python, from decorators to function wrappers to APIs that need to accept flexible input.
*args: Variable positional arguments
The *args syntax collects any number of positional arguments into a tuple. The * operator unpacks arguments when calling a function, and collects them when defining a function.
Basic *args example
def sum_all(*args):
"""Sum all provided numbers."""
total = 0
for num in args:
total += num
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(10, 20, 30, 40)) # 100
print(sum_all(5)) # 5
print(sum_all()) # 0
Inside the function, args is a tuple containing all the positional arguments passed to the function:
def show_args(*args):
print(f"Type: {type(args)}")
print(f"Value: {args}")
show_args(1, 2, 3)
# Type: <class 'tuple'>
# Value: (1, 2, 3)
Combining regular parameters with *args
You can mix regular parameters with *args. Regular parameters must come before *args:
def greet(greeting, *names):
"""Greet multiple people with the same greeting."""
for name in names:
print(f"{greeting}, {name}!")
greet("Hello", "Alice", "Bob", "Charlie")
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!
Unpacking with *args
You can also use * to unpack a sequence when calling a function:
def add(a, b, c):
return a + b + c
numbers = [1, 2, 3]
print(add(*numbers)) # 6
# Equivalent to: add(1, 2, 3)
This is particularly useful when you have a list or tuple and want to pass its elements as separate arguments:
def multiply(x, y, z):
return x * y * z
values = [2, 3, 4]
result = multiply(*values) # 24
**kwargs: Variable keyword arguments
The **kwargs syntax collects any number of keyword arguments into a dictionary. The ** operator unpacks keyword arguments when calling a function, and collects them when defining a function.
Basic **kwargs example
def print_info(**kwargs):
"""Print all provided keyword arguments."""
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=30, city="New York")
# name: Alice
# age: 30
# city: New York
Inside the function, kwargs is a dictionary containing all the keyword arguments:
def show_kwargs(**kwargs):
print(f"Type: {type(kwargs)}")
print(f"Value: {kwargs}")
show_kwargs(a=1, b=2, c=3)
# Type: <class 'dict'>
# Value: {'a': 1, 'b': 2, 'c': 3}
Combining regular parameters with **kwargs
You can mix regular parameters with **kwargs. Regular parameters and *args must come before **kwargs:
def create_profile(name, **details):
"""Create a user profile with a name and optional details."""
profile = {"name": name}
profile.update(details)
return profile
user = create_profile("Alice", age=30, city="NYC", role="Developer")
print(user)
# {'name': 'Alice', 'age': 30, 'city': 'NYC', 'role': 'Developer'}
Unpacking with **kwargs
You can use ** to unpack a dictionary when calling a function:
def greet_person(name, age, city):
return f"{name}, {age}, from {city}"
info = {"name": "Alice", "age": 30, "city": "New York"}
print(greet_person(**info))
# Alice, 30, from New York
This is useful when you have a dictionary and want to pass its key-value pairs as keyword arguments:
def configure_app(host, port, debug):
print(f"Host: {host}, Port: {port}, Debug: {debug}")
config = {"host": "localhost", "port": 8000, "debug": True}
configure_app(**config)
# Host: localhost, Port: 8000, Debug: True
Using *args and **kwargs together
You can use both *args and **kwargs in the same function. The order must be: regular parameters, *args, then **kwargs:
def example_func(required, *args, **kwargs):
print(f"Required: {required}")
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
example_func("hello", 1, 2, 3, name="Alice", age=30)
# Required: hello
# Args: (1, 2, 3)
# Kwargs: {'name': 'Alice', 'age': 30}
Practical example: Flexible logger
def log(message, *values, level="INFO", **metadata):
"""Log a message with optional values and metadata."""
print(f"[{level}] {message}")
if values:
print(f" Values: {values}")
if metadata:
print(f" Metadata: {metadata}")
log("Processing started", 1, 2, 3, level="DEBUG", user="Alice", task="import")
# [DEBUG] Processing started
# Values: (1, 2, 3)
# Metadata: {'user': 'Alice', 'task': 'import'}
Common use cases
Function wrappers and decorators
You'll learn about decorators in depth in the coming sections. This example shows how *args and **kwargs are used with decorators to pass arguments through wrapper functions.
*args and **kwargs are essential for creating wrapper functions that need to pass arguments to another function:
def log_calls(func):
"""Decorator that logs function calls."""
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
@log_calls
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
greet("Alice")
# Output:
# Calling greet with args=('Alice',), kwargs={}
# greet returned: Hello, Alice!
# Hello, Alice!
greet("Bob", greeting="Hi")
# Output:
# Calling greet with args=('Bob',), kwargs={'greeting': 'Hi'}
# greet returned: Hi, Bob!
# Hi, Bob!
Flexible API functions
When building APIs or functions that need to accept various optional parameters:
def create_user(username, email, *roles, **profile):
"""Create a user with required info, optional roles, and profile data."""
user = {
"username": username,
"email": email,
"roles": list(roles),
"profile": profile
}
return user
user = create_user(
"alice",
"alice@example.com",
"admin",
"editor",
first_name="Alice",
last_name="Smith",
age=30
)
print(user)
# {'username': 'alice', 'email': 'alice@example.com',
# 'roles': ['admin', 'editor'],
# 'profile': {'first_name': 'Alice', 'last_name': 'Smith', 'age': 30}}
This function takes all the provided information—required username and email, any number of optional roles (via *roles), and any additional profile data (via **profile), and returns a structured dictionary containing all of that information about the person. The *roles collects any number of role strings into a list, while **profile collects any keyword arguments into a profile dictionary, allowing the function to accept flexible combinations of user data.
Forwarding arguments
Passing arguments from one function to another without knowing what they are:
def process_data(data, *args, **kwargs):
"""Process data and forward extra arguments to format_output."""
processed = data.upper()
return format_output(processed, *args, **kwargs)
def format_output(text, prefix="", suffix="", style="normal"):
"""Format output text with optional prefix, suffix, and style."""
return f"{prefix}{text}{suffix}"
result = process_data("hello", prefix=">>> ", suffix=" <<<")
print(result) # >>> HELLO <<<
Important notes
Parameter order matters
When using *args and **kwargs together, the order must be:
- Regular positional parameters
*args- Regular keyword-only parameters (if any)
**kwargs
# Correct order
def correct(required, *args, keyword_only=None, **kwargs):
pass
# This would cause a SyntaxError
# def wrong(required, **kwargs, *args):
# pass
Empty *args and **kwargs
Both *args and **kwargs can be empty:
def example(*args, **kwargs):
print(f"Args: {args}") # Empty tuple: ()
print(f"Kwargs: {kwargs}") # Empty dict: {}
example() # Both are empty
Mixing with default parameters
You can combine default parameters with *args and **kwargs:
def example(a, b=10, *args, c=20, **kwargs):
print(f"a={a}, b={b}, args={args}, c={c}, kwargs={kwargs}")
example(1, 2, 3, 4, c=30, d=40)
# a=1, b=2, args=(3, 4), c=30, kwargs={'d': 40}
Common pitfalls
Forgetting to unpack
When calling a function with *args or **kwargs, remember to unpack:
def add(a, b, c):
return a + b + c
numbers = [1, 2, 3]
# Wrong - passes the list as a single argument
# add(numbers) # TypeError
# Correct - unpacks the list
add(*numbers) # 6
Keyword arguments in *args
Keyword arguments that don't match parameter names go to **kwargs, not *args:
def example(*args, **kwargs):
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
example(1, 2, 3, x=10, y=20)
# Args: (1, 2, 3)
# Kwargs: {'x': 10, 'y': 20}
Modifying *args or **kwargs
Since *args is a tuple (immutable), you can't modify it directly. **kwargs is a dictionary (mutable), so you can modify it:
def example(*args, **kwargs):
# args[0] = 99 # TypeError: tuples are immutable
kwargs['new_key'] = 'new_value' # OK: dicts are mutable
return args, kwargs