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.
Before this lesson: Python Function Arguments
2*args and **kwargs: diagram
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.
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.
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:
- normal parameters,
- parameters with defaults,
*args,- keyword-only parameters (anything after
*argscan only be given by name), **kwargs, always last.
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.
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.
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*5or*None; only lists, tuples and other iterables can be spread.SyntaxError: arguments cannot follow var-keyword argument:**kwargshas to be the last parameter in the def.
Quick reference: *args and **kwargs
- Python Functions Cheat Sheet: def, args, lambda, scope — Define a Python function with `def name(parameters):` and `return` a result; use default values, `*args` and …
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.
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.
- Line 1
customertakes the first value.*pricescollects every other positional value into a tuple.tax_ratecomes after*prices, so it is keyword-only and has a default.**feescollects any remaining keyword arguments into a dictionary. - Line 2
sum(prices)adds up the tuple. With no prices it would be an empty tuple and the sum would be 0. - Line 3
fees.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. - Line 4The tax is applied to the items only, the fees are added afterwards, and
round()keeps two decimals. - Line 5
len(prices)counts how many positional prices were passed. - Line 7Three prices arrive in
prices: 12.5 + 7.5 + 30 = 50, plus 10% tax gives 55.0. No fees, sofeesis an empty dictionary. - Line 8
tax_rate=0.2is keyword-only and overrides the default.shippingandgift_wrapmatch no parameter, so they land infeesas{'shipping': 5, 'gift_wrap': 2.5}: 100 * 1.2 + 7.5 = 127.5. - Line 10-11
*basketspreads the list into two separate positional arguments, exactly as if you had typed4.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"))✓ 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))✓ 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"))✓ 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))✓ 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}>"✓ 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 ✓ CompletedWrite 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.
def average(*scores):
# return the average, or 0 when there are no scores
pass
# call average(70, 80, 90) and print the result
scores is a tuple. sum(scores) / len(scores) works, but dividing by len(scores) fails when it is 0, so check for an empty tuple first.def average(*scores):
if not scores:
return 0
return sum(scores) / len(scores)
print(average(70, 80, 90))
2. Predict what gets collected
Predict the output Medium ✓ CompletedRead 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.
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)
first takes the first positional value, rest is a tuple of the other positional values, and extras is a dict of the unmatched keywords. A one-item tuple prints with a trailing comma, and *nums spreads 7 and 8 into two arguments.1 () {}
1 (2, 3) {}
1 (2,) {'key': 'x'}
7 (8,) {'mode': 'fast'}
2 more exercises → 6 more examples
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