Blog

args and kwargs in Python

What the star and the double star actually do, the order the parameters have to be in, and how to pass arguments straight through to another function.

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

  • *args is a tuple of extra positional arguments, **kwargs is 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.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Leave a Reply