*args and **kwargs
Core Python Functions and Modules

Python *args and **kwargs

1*args and **kwargs: overview

*args collects any extra positional arguments into a tuple and **kwargs collects any extra keyword arguments into a dictionary, so one function can accept a flexible number of inputs; the same stars in a call unpack a list or dictionary into arguments.

GoalBy the end you can write functions that accept any number of arguments, order normal, default, *args, keyword-only and **kwargs parameters correctly, unpack lists and dictionaries in a call, and forward arguments to another function.

Before this lesson: Python Function Arguments

2*args and **kwargs: diagram

Python *args and **kwargsreport("Q1", 3, 4, unit="kg", fmt="short")parameterwhat it receivestitle"Q1"*args(3, 4)**kwargs{'unit': 'kg', 'fmt': 'short'}def report(title, *args, **kwargs)
Named parameters are filled first. Leftover positional arguments are packed into the tuple args, and leftover keyword arguments are packed into the dictionary kwargs.

3*args and **kwargs: explained step by step

Some functions need to accept a number of values you cannot know in advance. print("a", "b", "c") takes any number of items, and so does max(3, 9, 4). Python lets your own functions do the same with two special parameters, *args and **kwargs. The diagram above shows where each kind of argument ends up.

*args: any number of positional arguments

A parameter with one star collects every extra positional argument into a tuple. Inside the function you loop over it, index it or pass it to sum() like any other tuple. If the caller passes nothing extra, you get an empty tuple, never an error.

Python
def sum_all(*numbers):
    print(type(numbers).__name__, numbers)
    total = 0
    for n in numbers:
        total += n
    return total

print(sum_all(4, 6, 10))
print(sum_all())

The name args is only a habit. The star is what matters, so choose a name that says what the values are, such as *numbers or *messages.

**kwargs: any number of keyword arguments

A parameter with two stars collects every extra keyword argument into a dictionary. The keys are the argument names (as strings) and the values are what the caller passed. With no extra keywords, you get an empty dictionary.

Python
def build_tag(name, text="", **attributes):
    attrs = "".join(f' {key}="{value}"' for key, value in attributes.items())
    return f"<{name}{attrs}>{text}</{name}>"

print(build_tag("a", "Docs", href="/docs", target="_blank"))
print(build_tag("p", "Hello"))

name and text are matched first. Only the keyword arguments that match no named parameter (href and target) land in attributes. This is how a function can accept options it has never heard of.

Combining them: the order matters

When a def line mixes several kinds of parameters, they must appear in this order:

  1. normal parameters,
  2. parameters with defaults,
  3. *args,
  4. keyword-only parameters (anything after *args can only be given by name),
  5. **kwargs, always last.
Python
def log(level, *messages, sep=" ", **context):
    text = sep.join(messages)
    extra = ", ".join(f"{key}={value}" for key, value in context.items())
    return f"[{level}] {text}" + (f" ({extra})" if extra else "")

print(log("INFO", "Server", "started"))
print(log("WARN", "Disk", "almost", "full", sep="-", host="web1", free="3%"))

Here level takes the first value, messages swallows the rest of the positional ones, sep can only be given by name, and context collects everything else.

Unpacking when you call

The same stars work in a call, in the opposite direction. * spreads a list, tuple or other iterable into separate positional arguments. ** spreads a dictionary into keyword arguments. You can use both, and more than once, in the same call.

Python
def make_url(host, path, secure=True):
    scheme = "https" if secure else "http"
    return f"{scheme}://{host}/{path}"

parts = ["example.com", "docs"]
options = {"secure": False}
print(make_url(*parts))
print(make_url(*parts, **options))
print(make_url(**{"host": "example.com", "path": "login"}))

Unpacking is also the only way to pass a keyword that is a reserved word. You cannot write class="btn", but **{"class": "btn"} works fine.

Forwarding arguments to another function

A common job for *args and **kwargs is a wrapper: a function that adds something and hands every other argument to a second function unchanged. It does not need to know what the second function accepts.

Python
def make_price(amount, currency="USD", decimals=2):
    return f"{currency} {amount:.{decimals}f}"

def labelled(label, *args, **kwargs):
    return label + ": " + make_price(*args, **kwargs)

print(labelled("Total", 49.5))
print(labelled("Total", 49.4, currency="EUR", decimals=0))

If make_price gains a new option tomorrow, labelled already supports it. Decorators, which come later, are built on exactly this pattern.

When not to use them

Flexible parameters hide what a function expects. A reader cannot see the options in the def line, and a typo such as hreff="/docs" is accepted silently because **attributes takes any name. If the set of inputs is fixed and known, write ordinary named parameters. Reach for *args and **kwargs when you really do not know the inputs (collecting values, wrapping other functions, passing options through).

Errors to recognise

  • SyntaxError: positional argument follows keyword argument: in a call, put positional arguments first.
  • TypeError: ... got multiple values for argument 'x': a name was filled twice, for example by a positional value and again by **options.
  • TypeError: ... argument after * must be an iterable: you wrote *5 or *None; only lists, tuples and other iterables can be spread.
  • SyntaxError: arguments cannot follow var-keyword argument: **kwargs has to be the last parameter in the def.

Quick reference: *args and **kwargs

4*args and **kwargs: syntax

def f(a, b=1, *args, c, d=2, **kwargs):
    ...                      # order: normal, defaults, *args, keyword-only, **kwargs
    # args   -> tuple of the extra positional arguments
    # kwargs -> dict of the extra keyword arguments

f(1, 2, 3, 4, c=5, e=6)      # a=1, b=2, args=(3, 4), c=5, d=2, kwargs={"e": 6}

values = [1, 2, 3]
options = {"c": 5, "e": 6}
f(*values, **options)        # unpack a list into positionals, a dict into keywords

def wrapper(*args, **kwargs):
    return target(*args, **kwargs)   # forward everything unchanged

5*args and **kwargs: run the example

Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.

Interactive example
def checkout(customer, *prices, tax_rate=0.1, **fees):
    subtotal = sum(prices)
    extras = sum(fees.values())
    total = round(subtotal * (1 + tax_rate) + extras, 2)
    return f"{customer}: {len(prices)} item(s), total {total}"

print(checkout("Asha", 12.5, 7.5, 30))
print(checkout("Ben", 100, tax_rate=0.2, shipping=5, gift_wrap=2.5))

basket = [4.0, 6.0]
print(checkout("Cara", *basket, shipping=3))
Expected outputAsha: 3 item(s), total 55.0
Ben: 1 item(s), total 127.5
Cara: 2 item(s), total 14.0

7*args and **kwargs: how the example works

Hover or tap a step to highlight its lines in the example above.

  1. Line 1customer takes the first value. *prices collects every other positional value into a tuple. tax_rate comes after *prices, so it is keyword-only and has a default. **fees collects any remaining keyword arguments into a dictionary.
  2. Line 2sum(prices) adds up the tuple. With no prices it would be an empty tuple and the sum would be 0.
  3. Line 3fees.values() holds the amounts of whatever extra fees the caller named, such as shipping or gift wrap. The function never needs to know those names in advance.
  4. Line 4The tax is applied to the items only, the fees are added afterwards, and round() keeps two decimals.
  5. Line 5len(prices) counts how many positional prices were passed.
  6. Line 7Three prices arrive in prices: 12.5 + 7.5 + 30 = 50, plus 10% tax gives 55.0. No fees, so fees is an empty dictionary.
  7. Line 8tax_rate=0.2 is keyword-only and overrides the default. shipping and gift_wrap match no parameter, so they land in fees as {'shipping': 5, 'gift_wrap': 2.5}: 100 * 1.2 + 7.5 = 127.5.
  8. Line 10-11*basket spreads the list into two separate positional arguments, exactly as if you had typed 4.0, 6.0. The subtotal 10.0 plus tax is 11.0, and shipping adds 3, giving 14.0.

8*args and **kwargs: common mistakes

Trying to change *args like a list

✗ Wrong

def collect(*items):
    items.append("extra")
    return items

print(collect("a", "b"))
AttributeError: 'tuple' object has no attribute 'append'

✓ Right

def collect(*items):
    items = list(items)
    items.append("extra")
    return items

print(collect("a", "b"))

*items is packed into a tuple, which cannot be changed. Convert it with list(items) when you need to modify it, or build a new tuple.

Passing a list without unpacking it

✗ Wrong

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

prices = [4, 8, 15]
print(sum_all(prices))
TypeError: unsupported operand type(s) for +=: 'int' and 'list'

✓ Right

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

prices = [4, 8, 15]
print(sum_all(*prices))

The whole list became ONE argument, so numbers is ([4, 8, 15],) and the loop tries to add a list to an int. Put a star in front of the list in the call so its items become separate arguments.

A positional argument after a keyword argument

✗ Wrong

def make_url(host, path, secure=True):
    return f"{host}/{path}"

print(make_url(host="example.com", "docs"))
SyntaxError: positional argument follows keyword argument

✓ Right

def make_url(host, path, secure=True):
    return f"{host}/{path}"

print(make_url("example.com", "docs"))
print(make_url(host="example.com", path="docs"))

Once you start naming arguments, every later argument must be named too. Put the positional values first, or name them all.

Supplying the same parameter twice with **

✗ Wrong

def price(amount, currency="USD"):
    return f"{currency} {amount}"

settings = {"currency": "EUR"}
print(price(5, "GBP", **settings))
TypeError: price() got multiple values for argument 'currency'

✓ Right

def price(amount, currency="USD"):
    return f"{currency} {amount}"

settings = {"currency": "EUR"}
print(price(5, **settings))

"GBP" already fills currency by position, and the dictionary fills it again by name. Decide which one should win and pass the value only once.

Putting **kwargs before other parameters

✗ Wrong

def build_tag(**attributes, name):
    return f"<{name}>"
SyntaxError: arguments cannot follow var-keyword argument

✓ Right

def build_tag(name, **attributes):
    return f"<{name}>"

print(build_tag("p", id="intro"))

**kwargs swallows all remaining keyword arguments, so nothing can come after it. Put it last in the def line.

9*args and **kwargs: exercises

Write your answer, then press Check. The checks run in your browser.

1. Average of any number of scores

Write code Easy

Write a function average(*scores) that accepts any number of scores and returns their average. When it is called with no scores it should return 0.

Then call it as average(70, 80, 90) and print the result.

Your code
def average(*scores):
    # return the average, or 0 when there are no scores
    pass

# call average(70, 80, 90) and print the result

2. Predict what gets collected

Predict the output Medium

Read how show collects its arguments and type exactly what the program prints, then press Check. Pay attention to how a tuple with one item is printed.

Read this code
def show(first, *rest, **extras):
    print(first, rest, extras)

show(1)
show(1, 2, 3)
show(1, 2, key="x")
nums = [7, 8]
opts = {"mode": "fast"}
show(*nums, **opts)

2 more exercises → 6 more examples

Finished this lesson?
Progress is saved in this browser — create a free account to keep it across devices.

Questions learners ask

What is the difference between *args and **kwargs in Python?

*args collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments (name=value pairs) into a dictionary. Use *args when you do not know how many values will come and **kwargs when you do not know which options will come.

Do I have to call them args and kwargs?

No. Only the stars matter: *numbers and **options work exactly the same. The names args and kwargs are a convention that other programmers instantly recognise, so use them for generic pass-through code and use a descriptive name when the values have a clear meaning.

What does the * mean when I put it in front of a list in a function call?

It unpacks the list into separate positional arguments, so f(*[1, 2, 3]) means f(1, 2, 3). A double star does the same for a dictionary, turning f(**{"a": 1}) into f(a=1). Without the star, the whole list would be passed as a single argument.

Further reading

10Next lesson

Up nextPython Lambda FunctionsA lambda is a small anonymous function written as a single expression, lambda parameters: expression, that returns the …

Related:
Python UnpackingPython DictionariesPython Function ArgumentsPython Decorators

← Python Function Arguments · All Functions and Modules lessons · Practise in the IDE