Chapter 20

Function arguments — names, defaults, and stars

Passing arguments by name, default values and the rule about their order, *args and **kwargs, and unpacking a list or dictionary into a call.

32 minPython 3.12
  1. 1Encounter
  2. 2Understand
  3. 3Worked
  4. 4Predict
  5. 5Apply
  6. 6Stretch

The problem we are solving

The functions in the last chapter could only be called one way: as many arguments as there are parameters, in exactly that order.

In practice that is not enough. Consider a function for an order:

python
def order(item, quantity):
    return f"{quantity} x {item}"


print(order("pen", 3))
print(order(3, "pen"))
text
3 x pen
pen x 3

The second line raised nothing — it simply gave a meaningless answer. When two parameters are the same kind of thing, Python has no way of telling that the order was reversed.

And if almost every order is for one item? You would type 1 every time. If a function has four optional settings? You would pass all four every time, including the ones you are not changing.

This chapter is the rest of the ways to pass arguments: by name, with defaults, and in unknown numbers.

By the end of this chapter you can

  • Pass arguments by name, and say when you should
  • Give defaults, and follow the rule about their order
  • Take any number of arguments with *args
  • Take arguments of any name with **kwargs
  • Unpack a list or a dictionary into a call

Prerequisites: Functions — one decision, one name.


Passing by name

Instead of remembering the order, write the name:

python
def order(item, quantity):
    return f"{quantity} x {item}"


print(order(quantity=3, item="pen"))
text
3 x pen

With names, the order stops mattering — quantity was written first and still landed in the right place.

When should you? When the argument does not explain itself. order("pen", 3) reads fine. But send(True, False) tells you nothing, while send(retry=True, verbose=False) does. As a rule: name them when passing True/False or a bare number.

Default values

python
def order(item, quantity=1):
    return f"{quantity} x {item}"


print(order("pen"))
print(order("pen", 5))
print(order("pen", quantity=5))
text
1 x pen
5 x pen
5 x pen

quantity=1 says "assume one if nobody says otherwise". The common case gets short, and the uncommon case is still expressible.

One rule has to be obeyed: parameters with defaults always come last.

text
def order(quantity=1, item):
    return item
text
SyntaxError: parameter without a default follows parameter with a default

The reason is clear once you think about it: given order("pen"), how would Python know which parameter "pen" is for? With the required ones first, there is no ambiguity.

Any number of arguments — *args

python
def total(*prices):
    return sum(prices)


print(total(15, 60))
print(total(15, 60, 850))
print(total())
text
75
925
0

The star in *prices says "gather up the remaining positional arguments here". No need to decide in advance how many will come, and none at all is fine.

What is the gathered thing?

python
def show(*things):
    print(type(things))
    print(things)


show(1, "two")
text
<class 'tuple'>
(1, 'two')

A tuple — chapter fourteen's tuple. The reasoning is neat: how many arguments there are is settled at the moment of the call and should not change afterwards, so an immutable container fits.

The name prices is not required. The conventional name is *args, which is what you will see in most code. But a meaningful name is better where one exists — *prices tells a reader what is arriving, *args does not.

Arguments of any name — **kwargs

python
def describe(**fields):
    print(type(fields))
    for key, value in fields.items():
        print(f"{key}: {value}")


describe(item="pen", quantity=3)
text
<class 'dict'>
item: pen
quantity: 3

Two stars gather the arguments passed by name, and the result is a dictionary — pairs of name and value, which is exactly what it should be.

One star gives a tuple, two give a dictionary. Remember that pairing and the rest follows.

The order is fixed

Written together, the order is: *plain, then `args, then ones with defaults, then kwargs`.

text
def f(a, *args, b=1, **kwargs):

It looks fussy at first, and the reason is simple: Python has to know which argument goes where, and since *args takes "everything else", no positional parameter can follow it.

Unpacking into a call

The stars can be used at the call too, where they do the opposite job:

python
def order(item, quantity):
    return f"{quantity} x {item}"


values = ["pen", 3]
fields = {"item": "bag", "quantity": 2}

print(order(*values))
print(order(**fields))
text
3 x pen
2 x bag

*values unpacks the list — the same as writing order(values[0], values[1]). **fields unpacks the dictionary, and its keys have to match the parameter names.

A star in a definition means gather; a star in a call means unpack — the same symbol, two directions.


A complete example

invoice.py:

python
# Arguments, in the order Python expects them
def invoice(customer, *items, currency="unit", discount=0.0, **extra):
    total = sum(price for _, price in items)
    after = total * (1 - discount)

    lines = [f"Invoice for {customer}"]
    for name, price in items:
        lines.append(f"  {name:<10} {price:>8.2f} {currency}")
    lines.append(f"  {'subtotal':<10} {total:>8.2f} {currency}")
    if discount:
        lines.append(f"  {'discount':<10} {discount * 100:>7.0f}%")
    lines.append(f"  {'total':<10} {after:>8.2f} {currency}")
    for key, value in extra.items():
        lines.append(f"  note {key}: {value}")
    return "\n".join(lines)


print(invoice("rafi", ("pen", 15.0), ("bag", 850.0)))
print()
print(invoice("dia", ("ink", 120.0), discount=0.1, reference="A-77"))
text
Invoice for rafi
  pen           15.00 unit
  bag          850.00 unit
  subtotal     865.00 unit
  total        865.00 unit

Invoice for dia
  ink          120.00 unit
  subtotal     120.00 unit
  discount        10%
  total        108.00 unit
  note reference: A-77

Four things worth noticing.

customer is required and everything else is optional. An invoice with no customer is meaningless, so that is a plain parameter. There may be no items at all, so *items.

discount=0.0 is not only a default, it simplifies a condition. The if discount: line uses chapter eight's truthiness rule — 0.0 is false, so with no discount that row is never printed.

`extra absorbs what the function does not know about.** reference="A-77" matches no parameter, so it lands in extra` and is printed as a note. That is how a function accepts fields nobody has thought of yet.

for _, price in items is chapter fourteen's unpacking, with the underscore saying the name is not needed here. items is a tuple of tuples, and each inner tuple is unpacked.


When it breaks

SyntaxError: parameter without a default follows parameter with a default A parameter with a default was placed before a required one. Required first, defaults after.

TypeError: order() takes 2 positional arguments but 3 were given Too many arguments. The message says both how many it takes and how many arrived.

TypeError: order() got an unexpected keyword argument 'qty' Passed by name, but no parameter has that name. Check the spelling — or note that a function with **kwargs would not have complained, it would have quietly absorbed it.

TypeError: order() got multiple values for argument 'item' The same parameter was given twice, once by position and once by name — order("pen", item="bag").

The function runs but the answer is the wrong way round The arguments were reversed, and Python could not tell because the types matched. Pass by name — this is the silent mistake this chapter opened with.

*Unpacking with ` but the counts do not match** In order(*values), values must hold exactly as many things as the function has parameters. Check with print(len(values))`.