W3docs

Python *args and **kwargs

Learn how *args and **kwargs let Python functions accept any number of positional and keyword arguments, with real examples and common patterns.

*args and **kwargs are special syntax in Python that let a function accept a variable number of arguments. *args collects extra positional arguments into a tuple, while **kwargs collects extra keyword arguments into a dictionary. Together they give you total flexibility — you can write functions that work with one argument or one hundred.

This page covers both features in depth: how they work, when to use them, how to combine them, and the common pitfalls to avoid.

What Is *args?

When you prefix a parameter name with a single asterisk (*), Python collects all extra positional arguments passed to the function into a tuple bound to that parameter name. The name args is a convention — you could write *numbers or *values — but *args is universally understood.

def add_all(*args):
    total = 0
    for n in args:
        total += n
    return total

print(add_all(1, 2, 3))         # 6
print(add_all(10, 20, 30, 40))  # 100
print(add_all())                 # 0

Inside the function, args is an ordinary tuple that you can loop over, index, or pass to other functions. Calling add_all() with zero arguments is valid — args is simply an empty tuple.

Mixing regular parameters with *args

Regular (positional) parameters come first; *args catches everything that follows:

def greet(greeting, *names):
    for name in names:
        print(greeting + ', ' + name + '!')

greet('Hello', 'Alice', 'Bob', 'Charlie')
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!

greeting is filled by the first argument; names receives the rest as a tuple. If you call greet('Hi') with no extra names, names is an empty tuple and the loop simply does not run — no error.

What Is **kwargs?

Two asterisks (**) before a parameter name tell Python to collect all extra keyword arguments into a dictionary. Again, kwargs is convention; any valid Python identifier works.

def describe(**kwargs):
    for key, value in kwargs.items():
        print(key + ': ' + str(value))

describe(name='Alice', age=30, city='New York')
# name: Alice
# age: 30
# city: New York

Inside the function, kwargs is an ordinary dictionary. You can loop over it, look up keys, or pass it on. The caller decides which keys to supply — none of them are fixed by the function definition.

When to reach for **kwargs

**kwargs shines when:

  • A function needs to accept a flexible, open-ended set of named options (configuration, metadata, HTML attributes).
  • You are writing a wrapper that must forward keyword arguments to another function without knowing what they are.
  • You want to build a dictionary from keyword arguments in a readable way (avoids the boilerplate of dict(key=value, ...)).

Combining *args and **kwargs

A single function can accept unlimited positional arguments and unlimited keyword arguments. The required order in the signature is:

  1. Normal positional parameters
  2. *args
  3. Keyword-only parameters (with defaults)
  4. **kwargs
def log_event(event, *tags, **metadata):
    print('Event:', event)
    print('Tags:', tags)
    print('Metadata:', metadata)

log_event('login', 'auth', 'user', user_id=42, ip='127.0.0.1')
# Event: login
# Tags: ('auth', 'user')
# Metadata: {'user_id': 42, 'ip': '127.0.0.1'}

event takes the first positional argument; tags catches the remaining positional arguments; metadata catches all keyword arguments.

Unpacking Arguments with * and **

The * and ** operators are not only for function definitions — they also work on the call side to unpack sequences and mappings into separate arguments.

Unpacking a list or tuple with *

def multiply(a, b, c):
    return a * b * c

nums = [2, 3, 4]
print(multiply(*nums))  # 24

*nums unpacks the list so that a=2, b=3, c=4. This is equivalent to writing multiply(2, 3, 4). See Unpack Tuples for more on the unpacking operator.

Unpacking a dictionary with **

def power(base, exp):
    return base ** exp

params = {'base': 3, 'exp': 4}
print(power(**params))  # 81

**params maps each dictionary key to the matching parameter name. This is useful when arguments are stored in a configuration dictionary built at runtime.

Keyword-Only Arguments After *args

Any parameter listed after *args in the signature can only be passed by name (it becomes a keyword-only argument). This is a clean way to add optional flags without ambiguity:

def configure(host, *args, port=80, debug=False):
    print('host:', host)
    print('extra:', args)
    print('port:', port)
    print('debug:', debug)

configure('localhost', 'arg1', port=8080, debug=True)
# host: localhost
# extra: ('arg1',)
# port: 8080
# debug: True

port and debug cannot be set positionally because *args already consumes all positional overflow. This pattern is common in library APIs — users must write port=8080 explicitly, which makes call sites self-documenting.

For a detailed explanation of Python's scoping rules, see Python Scope.

Forwarding Arguments to Another Function

One of the most practical uses of *args/**kwargs is writing wrappers and decorators that pass arguments through to an inner function without knowing what those arguments are:

def add_all(*args):
    return sum(args)

def wrapper(*args, **kwargs):
    print('Calling with args:', args, 'kwargs:', kwargs)
    return add_all(*args)

print(wrapper(1, 2, 3))
# Calling with args: (1, 2, 3) kwargs: {}
# 6

This pattern appears throughout Python's standard library and is the foundation of decorators and higher-order functions.

Full Signature Order

Python enforces a strict ordering rule for all parameter kinds. The complete order is:

PositionKindExample
1Positional-only (Python 3.8+)a, b, /
2Normal positional-or-keywordx, y
3Variable positional*args
4Keyword-onlyflag=True
5Variable keyword**kwargs

Breaking this order is a SyntaxError. A function that uses all five kinds looks like:

def full_sig(pos1, pos2, /, normal, *args, kw_only, **kwargs):
    print(pos1, pos2, normal, args, kw_only, kwargs)

full_sig(1, 2, 3, 4, 5, kw_only='k', extra='e')
# 1 2 3 (4, 5) k {'extra': 'e'}

In everyday code you rarely need all five at once. The most common patterns are *args alone, **kwargs alone, or *args followed by **kwargs.

Type Annotations

You can annotate *args and **kwargs with type hints. The annotation applies to each individual item, not to the tuple or dict itself:

from typing import Any

def add_all(*args: float) -> float:
    return sum(args)

def describe(**kwargs: Any) -> None:
    for key, value in kwargs.items():
        print(f'{key}: {value}')

print(add_all(1.5, 2.5, 3.0))  # 7.0
describe(name='Bob', score=99)
# name: Bob
# score: 99

*args: float means every element of args is expected to be a float. **kwargs: Any means values can be anything. This keeps static-analysis tools happy while preserving runtime flexibility.

Common Pitfalls

1. Wrong argument order in the signature

Putting **kwargs before *args is a SyntaxError:

# Wrong — raises SyntaxError
# def bad(name, **kwargs, *args): ...

# Correct
def good(name, *args, **kwargs):
    pass

2. Mutating the args tuple

args is a tuple and therefore immutable. If you need to modify the arguments, convert to a list first:

def double_all(*args):
    items = list(args)      # mutable copy
    items = [x * 2 for x in items]
    return items

print(double_all(1, 2, 3))  # [2, 4, 6]

3. Shadowing a required parameter name

If you use *args and also have a keyword argument with the same name as a positional parameter, callers can get confused. Keep parameter names distinct and use keyword-only parameters (after *args) for optional flags.

4. Over-using **kwargs instead of explicit parameters

**kwargs hides what a function actually accepts, making autocompletion and static analysis harder. Prefer explicit parameters for the options your function genuinely supports; use **kwargs only when the set of options is truly open-ended or when forwarding to another function.

Summary

FeatureSyntaxWhat it collectsType inside function
Variable positional args*argsExtra positional argumentstuple
Variable keyword args**kwargsExtra keyword argumentsdict
Unpack sequence on callfunc(*seq)List/tuple → positional args
Unpack mapping on callfunc(**mapping)Dict → keyword args

For closely related topics, see Python Functions for function basics, Python Lambda for anonymous functions, and Python Scope for how Python resolves variable names.

Practice

Practice
In Python, what does *args do when used in a function definition?
In Python, what does *args do when used in a function definition?
Was this page helpful?