FunctionsChapter 50 of 114
args and kwargs
Accept any number of positional or keyword arguments.
*args collects extra positional arguments
def total(*numbers):
return sum(numbers)
print(total(1, 2))
print(total(1, 2, 3, 4))
print(total())Output
3 10 0
Inside the function, numbers is a tuple of whatever was passed. The name args is only a convention — the * is what does the work — but stick to it, because everyone recognises it.
def show(*args):
print(type(args), args)
show(1, "two")Output
<class 'tuple'> (1, 'two')
**kwargs collects extra keyword arguments
def show(**kwargs):
print(type(kwargs), kwargs)
for key, value in kwargs.items():
print(f"{key} = {value}")
show(colour="red", size=10)Output
<class 'dict'> {'colour': 'red', 'size': 10}
colour = red
size = 10Here it is a dictionary, keyed by the argument names.
Combining them
Ordinary parameters come first, then *args, then keyword-only parameters, then **kwargs:
def report(title, *rows, separator=" | ", **options):
print(title)
print(separator.join(rows))
print(options)
report("Names", "Ada", "Grace", separator=", ", colour="red")Output
Names
Ada, Grace
{'colour': 'red'}Note that separator was picked out by name and did not land in options. Anything named that matches a real parameter goes there; only leftovers go into **kwargs.
Passing them on
The most common real use is a wrapper that accepts anything and hands it straight to something else:
def log_call(fn, *args, **kwargs):
print("calling", fn.__name__, "with", args, kwargs)
return fn(*args, **kwargs)
def area(width, height=2):
return width * height
print(log_call(area, 3, height=4))Output
calling area with (3,) {'height': 4}
12The * and ** do opposite jobs depending on where they appear: in a definition they collect, in a call they spread.
Unpacking in a call
def describe(name, age):
print(f"{name} is {age}")
values = ("Ada", 36)
details = {"name": "Grace", "age": 45}
describe(*values)
describe(**details)Output
Ada is 36 Grace is 45
The dictionary's keys have to match the parameter names exactly, or you get a TypeError about an unexpected argument.
Do not reach for it too early
*args makes a function flexible and its signature useless. A reader can no longer see what it takes, and neither can their editor:
def draw(*args):
print(args)
draw(1, 2, 3, 4)Output
(1, 2, 3, 4)
If the arguments have meanings, name them. Save *args for genuine pass-through wrappers and for functions that really do take an unbounded list of the same kind of thing, like sum or print.
Test yourself
2 questionsWhat type is args inside 'def total(*args)'?
Show the answer
tuple — kwargs is the dictionary. args is always a tuple.
What do * and ** do at a call site rather than in a definition?
Show the answer
Spread a sequence or dictionary into arguments — In a definition they collect, in a call they spread. Same symbols, opposite jobs.
Default Values
Make a parameter optional, and avoid the shared-default trap.