Question

Python *args and **kwargs: Variable Function Arguments

pythonargskeyword-argument

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:

  • *args collects extra positional arguments into a tuple.
  • **kwargs collects 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: name and age each expect one specific package.
  • *args is a box for any extra unlabelled packages that arrive in order.
  • **kwargs is a filing cabinet for any extra labelled packages, such as color="blue" or priority=True.

When calling a function, the direction can reverse:

  • *a_list opens a sequence and hands over its items one by one.
  • **a_dict opens a dictionary and hands over its key-value pairs as named arguments.
Take Quiz

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:

  1. Python assigns the first positional value, "Math", to category.

  2. The remaining positional values, 80, 90, and 70, are collected into scores:

    scores == (80, 90, 70)
    
  3. The named argument label="Final score" is collected into settings:

    settings == {"label": "Final score"}
    
  4. sum(scores) is 240, and len(scores) is 3, so average is 80.0.

  5. settings.get("label", "Average") finds the supplied label.

  6. 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, and debug.

    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)
    
Take Quiz

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

FeatureCollects in a function definitionUnpacks in a function callResult or requirement
*valuesExtra positional argumentsA list, tuple, or other iterableCollected as a tuple
**optionsExtra keyword argumentsA dictionary with string keysCollected as a dictionary
Normal parameterOne expected argumentOne valueAssigned directly
Keyword-only parameterA named option after *Must be passed as name=valueClear, 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.

Take Quiz

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})
  • args and kwargs are conventions; the names may be changed.
  • *args becomes a tuple inside the function.
  • **kwargs becomes 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.

Take Quiz

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.

Take Quiz

Keep learning

Related questions

View more