*args, **kwargs & unpacking
Write functions that accept any number of arguments, and unpack collections into calls.
Why flexible signatures matter
Some functions cannot know their arity ahead of time. A decorator has to wrap a function whose signature it has never seen, so it must forward whatever it is given. A logging helper should accept log("saved", user, count) with as many values as the caller has. print, max, and str.format all take a variable number of arguments for the same reason. Without *args and **kwargs you would hard-code a fixed parameter count and rewrite the function every time a caller needs one more slot. These two features let one function absorb any call shape, and the mirror-image * and ** at the call site let you feed a collection you already hold into any function.
*args: collect extra positionals into a tuple
A parameter written *name sweeps up every positional argument that did not match an earlier parameter and binds them as a tuple:
def total(*nums):
return sum(nums) # nums is a tuple, e.g. (1, 2, 3)
total(1, 2, 3) # 6
total() # 0 (nums is the empty tuple ())
The name args is only a convention. What matters is the leading *. Because nums is a real tuple, sum(nums) works, and total() gives sum(()) == 0.
**kwargs: collect extra keywords into a dict
A parameter written **name gathers keyword arguments that did not match a named parameter into a dict:
def tag(name, **attrs):
return name, attrs
tag("a", href="/x", id=3) # ("a", {"href": "/x", "id": 3})
* and ** at the call site: spread a collection into a call
The same symbols run in reverse when you call a function. *seq spreads an iterable into positional arguments, and **mapping spreads a dict into keyword arguments. This is how you fill a template from a dict you already have:
values = {"name": "Ada", "age": 30}
"{name} is {age}".format(**values) # "Ada is 30"
format(**values) is exactly format(name="Ada", age=30). One pair of symbols, two mirror roles: * and ** collect inside a def, and spread inside a call.
The same two symbols do opposite jobs depending on where they appear. Sort each line by which job the star is doing.
Pitfall: passing a container where you meant to unpack it
| call | nums binds to | sum(nums) |
|---|---|---|
| total(1, 2, 3) | (1, 2, 3) | 6 |
| total() | () | 0 |
| total(nums) | ([1, 2, 3],) | TypeError |
| total(*nums) | (1, 2, 3) | 6 |
total(*nums) collects loose numbers, not a list. If you already hold a list, you must spread it, or it arrives as a single argument:
nums = [1, 2, 3]
total(nums) # nums bound as ONE arg -> sum(([1, 2, 3],)) -> TypeError
total(*nums) # 6 (spreads to total(1, 2, 3))
The formatting side has its own traps. A placeholder in the template with no matching key raises KeyError, and a literal brace in the text must be doubled as {{ so format does not read it as a slot.
Interview nuance: any parameter listed after *args becomes keyword-only. It can no longer be filled positionally, because *args has already claimed every remaining positional argument. So def f(a, *args, b) requires b to be passed by name, and f(1, 2, 3) raises TypeError: f() missing 1 required keyword-only argument: 'b'. Interviewers use this to check that you understand the argument-binding order (named parameters fill first, *args sweeps the rest, then keyword-only parameters and **kwargs), not just the syntax.
def total(*nums):
return sum(nums)
print(total(1, 2, 3)) # 6
print(total()) # 0
print("{name} is {age}".format(**{"name": "Ada", "age": 30})) # Ada is 30Apply
Your turn
The task this lesson builds to.
Implement total(*nums): accept any number of numbers and return their sum.
total(1, 2, 3) is 6; total() is 0. Use a *nums parameter.
3 hints and 4 automated checks are waiting in the workspace.
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Implement render(template, values): fill the {placeholder} slots in template using the
values dict.
For template = "{name} is {age}" and values = {"name": "Ada", "age": 30}, return
"Ada is 30". Unpack the dict with **.
2 hints and 3 automated checks are waiting in the workspace.