A star in front of a parameter collects the leftover positional arguments into a tuple. Two stars collect the leftover keyword arguments into a dict.
The names args and kwargs are only convention. The stars do the work.
One star collects positional arguments
Inside the function, args is a plain tuple. It is empty when nothing extra was passed.
# *args collects positional arguments
def total(*args):
return sum(args), type(args).__name__
print(total(1, 2, 3))
print(total())
It prints:
(6, 'tuple')
(0, 'tuple')
Two stars collect keyword arguments
kwargs is a plain dict, in the order the arguments were given.
# **kwargs collects keyword arguments
def describe(**kwargs):
return kwargs, type(kwargs).__name__
print(describe(name='ada', age=36))
It prints:
({'name': 'ada', 'age': 36}, 'dict')
The order is fixed
Normal parameters, then *args, then keyword-only parameters, then **kwargs. Python enforces this.
# the order is fixed
def mixed(first, *args, key=None, **kwargs):
return first, args, key, kwargs
print(mixed(1, 2, 3, key='k', extra=True))
It prints:
(1, (2, 3), 'k', {'extra': True})
Anything after *args can only be passed by name.
A bare star forces keyword-only arguments
If you do not want the extra positional arguments but do want callers to name things, use a bare star.
# a bare star forces keyword-only arguments
def connect(host, *, port=5432, timeout=30):
return host, port, timeout
print(connect('localhost', port=5433))
try:
connect('localhost', 5433)
except TypeError as err:
print(type(err).__name__ + ':', err)
It prints:
('localhost', 5433, 30)
TypeError: connect() takes 1 positional argument but 2 were given
This is worth doing for any function with several options, because connect(host, 5433, 60) tells the reader nothing.
The same stars unpack at the call site
In a call, the stars mean the opposite. They spread a sequence or a dict into arguments.
# unpacking at the call site
def point(x, y, z):
return x + y + z
coords = [1, 2, 3]
named = {'x': 1, 'y': 2, 'z': 3}
print(point(*coords), point(**named))
It prints:
6 6
Passing everything through
This is the pattern behind every wrapper and decorator. Accept anything, forward it unchanged.
# forwarding everything to another function
def logged(fn, *args, **kwargs):
print('calling', fn.__name__, 'with', args, kwargs)
return fn(*args, **kwargs)
print(logged(point, 1, 2, z=3))
It prints:
calling point with (1, 2) {'z': 3}
6
The wrapper does not need to know the signature of the function it wraps.
What to remember
*argsis a tuple of extra positional arguments,**kwargsis a dict of extra keyword arguments.- The order is: normal,
*args, keyword-only,**kwargs. - A bare
*makes the parameters after it keyword-only. - At a call site the stars unpack instead of collect.
If you find yourself writing *args, **kwargs on a function that is not a wrapper, it is usually worth naming the parameters instead. The signature is documentation.