Home Tutorials Functions

Functions

*args and **kwargs in Python: Flexible Function Signatures Explained

Pyford Notes July 1, 2026 7 min read
Key points
  • *args collects extra positional arguments into a tuple.
  • **kwargs collects extra keyword arguments into a dict.
  • The names args and kwargs are conventions; the * and ** are what matter.
  • Use *iterable and **mapping at the call site to unpack sequences and dicts into arguments.

*args: variable positional arguments

Prefix a parameter name with * in a function definition and Python collects all extra positional arguments into a tuple under that name:

def add(*numbers):
    return sum(numbers)

add(1, 2)           # 3
add(1, 2, 3, 4, 5)  # 15
add()               # 0 (empty tuple)

You can have regular parameters before *args. Those are filled first by position; everything remaining goes into the tuple:

def log(level, *messages):
    prefix = f"[{level.upper()}]"
    for msg in messages:
        print(prefix, msg)

log("info", "server started", "port 8080")
# [INFO] server started
# [INFO] port 8080

**kwargs: variable keyword arguments

Prefix with ** to collect all extra keyword arguments into a dict:

def configure(**settings):
    for key, value in settings.items():
        print(f"  {key} = {value!r}")

configure(host="localhost", port=5432, ssl=True)
# host = 'localhost'
# port = 5432
# ssl = True

Like *args, regular parameters can appear before **kwargs and will be filled first:

def create_user(username, email, **profile):
    user = {"username": username, "email": email}
    user.update(profile)
    return user

create_user("alice", "[email protected]", age=30, role="admin")
# {"username": "alice", "email": "[email protected]", "age": 30, "role": "admin"}

Combining *args and **kwargs

A function can accept both; positional first, keyword after:

def debug(*args, **kwargs):
    print("Args:", args)
    print("Kwargs:", kwargs)

debug(1, 2, name="test", verbose=True)
# Args: (1, 2)
# Kwargs: {"name": "test", "verbose": True}

The conventional order in a full signature is: regular params, *args, keyword-only params, **kwargs.

Unpacking at the call site

The * and ** operators also work in reverse at a call site: they unpack a sequence or mapping into individual arguments:

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

args = ("World",)
kwargs = {"greeting": "Hi"}

greet(*args)              # "Hello, World!"
greet(**kwargs, name="Bob")  # "Hi, Bob!"
greet(*args, **kwargs)    # "Hi, World!"

This is frequently used to forward unknown arguments down to another function without explicitly naming them:

numbers = [5, 1, 3, 9, 2]
print(*numbers)          # "5 1 3 9 2" (unpacks into print's *args)

coords = [3.0, 4.0]
distance = math.dist(*coords, [0, 0])   # unpacks two arguments

Forwarding arguments through wrappers

Decorators typically use both to forward all arguments to the wrapped function unchanged:

from functools import wraps
import time

def timed(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)   # forward everything
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} took {elapsed:.3f}s")
        return result
    return wrapper

Keyword-only parameters

A parameter appearing after a bare * in a signature can only be passed by name, not by position. This is useful for options that have sensible defaults but should not be passed accidentally:

def fetch(url, *, timeout=30, retries=3):
    ...

fetch("https://example.com")                     # uses defaults
fetch("https://example.com", timeout=10)          # explicit keyword
fetch("https://example.com", 10)                  # TypeError: positional not allowed

When to use each

PatternUse case
*argsFunctions like max(), print(), math operations that accept 1 to N values of the same kind
**kwargsConfig builders, wrappers that accept options they pass on without knowing them in advance
Unpacking *listSpreading a list into positional args; merging lists in an expression
Unpacking **dictMerging dicts; passing a stored parameter set to a function call
Bare * in signatureForcing callers to use keyword syntax for clarity on functions with many options
Type hints for *args and **kwargs Annotate the element type, not the container: def f(*args: int, **kwargs: str) means each positional arg is an int and each keyword value is a str. The annotation applies to individual items, not the tuple or dict itself.