pyguides

*args and **kwargs Explained

When you write functions in Python, you often know exactly how many parameters you need. But sometimes you want to create a function that can accept any number of arguments — whether that’s one, ten, or none at all. This is where *args and **kwargs come in. They let you build flexible functions that handle varying amounts of input data.

You will learn what *args and **kwargs do, how to use them in your own functions, and when they are most useful in real-world code.

Understanding Positional Arguments

Before diving into *args, let’s quickly review how regular function parameters work. When you define a function with named parameters, each argument you pass must be provided in the correct order:

def greet(name, greeting):
    print(f"{greeting}, {name}!")

greet("Alice", "Hello")  # Output: Hello, Alice!
greet("Bob", "Hi")       # Output: Hi, Bob!

This works well when you know exactly what you need. But what if you want to create a function that can greet any number of people?

Using *args for Variable Positional Arguments

The asterisk (*) before args tells Python to collect any additional positional arguments into a tuple. This lets your function accept zero or more arguments:

def greet_all(*names):
    for name in names:
        print(f"Hello, {name}!")

greet_all("Alice", "Bob", "Charlie")
# Output:
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!

greet_all("Diana")
# Output:
# Hello, Diana!

greet_all()  # No output — the tuple is empty

Inside the function, Python collects every extra positional argument into a single tuple. You can iterate over that tuple, check its length, or access individual elements by index — the same operations you would perform on any other tuple. The receiving parameter is always a tuple regardless of how many arguments were passed, or even if none were passed at all.

The name “args” is just a widely followed convention. You can use any valid parameter name preceded by a single asterisk, such as “numbers” or “items”, and the behavior remains identical. The asterisk is what tells Python to pack excess positional arguments into a tuple. Choosing a descriptive name for your function’s domain makes the code more readable for anyone maintaining it later.

A Practical Example

Here is a function that calculates the sum of any number of values using the variadic positional argument pattern:

def sum_all(*numbers):
    total = 0
    for num in numbers:
        total += num
    return total

print(sum_all(1, 2, 3))        # Output: 6
print(sum_all(10, 20, 30, 40))  # Output: 100
print(sum_all())                # Output: 0

This approach is cleaner than writing separate functions for different argument counts, or forcing callers to pass a list every time. The caller can pass arguments naturally, and the function handles any number of them without extra ceremony.

Understanding Keyword Arguments

Positional arguments are ordered by position in the call, but keyword arguments are matched by name instead. When you pass a value using the name equals value syntax, Python binds that value to the parameter with the matching name, regardless of position. This makes function calls more readable when there are many parameters, and it lets callers skip optional arguments without worrying about argument order.

Here is a function that uses keyword arguments explicitly:

def create_user(username, email, age):
    return {"username": username, "email": email, "age": age}

user = create_user(username="alice", email="alice@example.com", age=30)

The function above requires exactly three keyword arguments — no more, no less. But what if you want to allow any number of extra properties without declaring every possible field in the function signature? That is where the double-asterisk pattern becomes essential, because it lets the caller supply whatever keyword arguments make sense for their use case.

Using **kwargs for Variable Keyword Arguments

The double asterisk before a parameter name collects any additional keyword arguments into a dictionary. Each key becomes a string matching the argument name, and each value is whatever was passed. This is the keyword-argument counterpart to the single-asterisk positional pattern.

def print_info(**info):
    for key, value in info.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, city="London")
# Output:
# name: Alice
# age: 30
# city: London

Just like the positional variadic pattern, the word “kwargs” is a convention and not a language requirement. You can name the parameter “options” or “config” or anything that fits your domain. What matters is the double asterisk, which instructs Python to collect unmatched keyword arguments into a dictionary. Inside the function body, you work with that dictionary the same way you would work with any other dictionary — iterating over keys and values, checking for the presence of specific keys, or passing the whole dictionary to another function via dictionary unpacking.

A Practical Example

Here is a function that builds a user profile with a required field plus any number of optional fields:

def create_profile(username, **extra_fields):
    profile = {"username": username}
    profile.update(extra_fields)
    return profile

profile1 = create_profile("alice", email="alice@example.com", bio="Developer")
print(profile1)
# Output: {'username': 'alice', 'email': 'alice@example.com', 'bio': 'Developer'}

profile2 = create_profile("bob", age=25, country="USA", hobby="Running")
print(profile2)
# Output: {'username': 'bob', 'age': 25, 'country': 'USA', 'hobby': 'Running'}

This pattern appears throughout the Python ecosystem. Libraries like Django, SQLAlchemy, and requests use keyword-argument collecting to let callers pass optional configuration without the library author having to anticipate every possible setting. The function receives a single dictionary and forwards or inspects it as needed.

Combining *args and **kwargs

Both patterns can coexist in a single function signature. When they do, the positional-variadic parameter must appear before the keyword-variadic parameter — this ordering is enforced by the language to prevent ambiguity about which arguments belong where.

def flexible_function(*args, **kwargs):
    print("Positional arguments:", args)
    print("Keyword arguments:", kwargs)

flexible_function(1, 2, 3, name="Alice", age=30)
# Output:
# Positional arguments: (1, 2, 3)
# Keyword arguments: {'name': 'Alice', 'age': 30}

This combination is particularly useful when you are building functions that act as wrappers around other functions — the wrapper can accept anything and forward everything without needing to know the wrapped function’s exact signature. Decorators, which are covered later in this tutorial, depend on this exact pattern.

Unpacking Arguments

The complementary operation to collecting arguments is spreading them back out. The same asterisk and double-asterisk syntax, when used in a function call instead of a function definition, unpacks a collection into individual arguments. A list or tuple unpacked with a single asterisk becomes positional arguments; a dictionary unpacked with a double asterisk becomes keyword arguments.

def introduce(name, age, city):
    print(f"I am {name}, {age} years old, from {city}.")

# Unpacking a list
details = ["Alice", 30, "London"]
introduce(*details)
# Output: I am Alice, 30 years old, from London.

# Unpacking a dictionary
info = {"name": "Bob", "age": 25, "city": "Paris"}
introduce(**info)
# Output: I am Bob, 25 years old, from Paris.

This is useful when you have data stored in a collection and want to pass it to a function that expects individual arguments.

Common Mistakes to Avoid

There are a few pitfalls to watch out for when using *args and **kwargs:

Putting **kwargs before *args

This will cause a syntax error:

# Wrong
def bad_function(**kwargs, *args):
    pass

# Correct order
def good_function(*args, **kwargs):
    pass

Mixing too many argument types

Arguments must be provided in a specific order: regular parameters first, then *args, then **kwargs. Python enforces this to avoid ambiguity:

def correct_order(a, b, *args, **kwargs):
    print(f"Regular: {a}, {b}")
    print(f"Args: {args}")
    print(f"Kwargs: {kwargs}")

correct_order(1, 2, 3, 4, 5, name="Alice", age=30)
# Output:
# Regular: 1, 2
# Args: (3, 4, 5)
# Kwargs: {'name': 'Alice', 'age': 30}

Using mutable defaults with *args

A common mistake is confusing *args with mutable default arguments. Remember that each call to the function gets a new empty tuple for *args, so you do not have the same problem as with mutable default values:

# This is fine — args is a new tuple each call
def append_to(*args):
    args = list(args)  # Convert to list if you need to modify
    args.append("new item")
    return args

print(append_to(1, 2, 3))  # Output: [1, 2, 3, 'new item']
print(append_to(4, 5))     # Output: [4, 5, 'new item'] — not [1, 2, 3, 'new item']!

This is different from the mutable default argument gotcha, where using a list as a default accumulates data across calls. With a variadic parameter, Python constructs a fresh tuple on every invocation, so each call starts with a clean slate regardless of what previous calls received.

Working with Built-in Functions

The standard library uses variadic arguments extensively. Many built-in functions accept a variable number of positional arguments, which is why you can call them with different argument counts without issue. The pattern is not just for advanced use — it is woven into the fabric of Python itself.

print("hello")           # 1 argument
print("hello", "world")  # 2 arguments
print(1, 2, 3, 4, 5)    # 5 arguments

The print function is defined roughly like this internally — it accepts a variadic positional parameter to handle any number of objects. Similarly, dictionary unpacking with double asterisks lets libraries like matplotlib and requests accept optional keyword configuration without requiring callers to provide every possible setting. The function simply inspects the dictionary for the keys it cares about and ignores the rest.

Real-World Use Cases

Beyond the standard library, variadic argument patterns appear in several concrete situations that every Python developer encounters. Here are some common scenarios:

Wrapper functions

When you build a function that calls another function, you often want to pass through whatever arguments were given:

def log_call(func, *args, **kwargs):
    print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
    result = func(*args, **kwargs)
    print(f"Result: {result}")
    return result

def add(a, b):
    return a + b

log_call(add, 3, 5)
# Output:
# Calling add with args=(3, 5), kwargs={}
# Result: 8

Class methods

Classes often use *args and **kwargs when defining methods that should be flexible, especially with inheritance:

class Database:
    def connect(self, *args, **kwargs):
        # Connection logic here
        print(f"Connecting with args={args}, kwargs={kwargs}")

db = Database()
db.connect(host="localhost", port=5432)

Decorators

Decorators are one of the most common places you will encounter both variadic patterns used together. Since a decorator wraps an arbitrary function, the wrapper cannot know the wrapped function’s signature in advance. By accepting any positional and keyword arguments and forwarding them to the original function, the decorator works with functions of any shape: no parameters, many parameters, or any mix of positional and keyword arguments.

def timer(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f} seconds")
        return result
    return wrapper

@timer
def slow_function():
    import time
    time.sleep(1)

slow_function()  # Outputs the time taken

Next Steps

You now understand how to write flexible functions that accept any number of arguments. This is a skill you’ll use frequently as you build more complex Python programs.

The next tutorial in the Python Fundamentals series covers String Formatting with f-strings. You’ll learn how to create dynamic, readable strings in Python — a skill that pairs well with what you learned here, since f-strings work great with data from *args and **kwargs.

For practice, try modifying one of your existing functions to accept variable arguments. See how it changes the way you can call that function.