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.
How do you want to learn?
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:
def order(item, quantity):
return f"{quantity} x {item}"
print(order("pen", 3))
print(order(3, "pen"))3 x pen
pen x 3The second line raised nothing — it simply gave a meaningless answer. Python does not check the types of arguments: here a string and a number swapped places, and because an f-string can print both, nothing stopped it. Python has no way of knowing 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
- Make some parameters keyword-only, so they can only be passed by name
- 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:
def order(item, quantity):
return f"{quantity} x {item}"
print(order(quantity=3, item="pen"))3 x penWith 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
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))
print(order("pen", quantity=5))1 x pen
5 x pen
5 x penquantity=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.
def order(quantity=1, item):
return itemSyntaxError: parameter without a default follows parameter with a defaultThe 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
def total(*prices):
return sum(prices)
print(total(15, 60))
print(total(15, 60, 850))
print(total())75
925
0The 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?
def show(*things):
print(type(things))
print(things)
show(1, "two")<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 namepricesis 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 —*pricestells a reader what is arriving,*argsdoes not.
Arguments of any name — **kwargs
def describe(**fields):
print(type(fields))
for key, value in fields.items():
print(f"{key}: {value}")
describe(item="pen", quantity=3)<class 'dict'>
item: pen
quantity: 3Two 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.
Parameters that can only be passed by name
A parameter that sits after *args cannot be passed by position, because *args takes every remaining positional argument for itself. So it can only be passed by name. This is called a keyword-only parameter:
def total(*prices, discount=0.0):
return sum(prices) * (1 - discount)
print(total(100, 50))
print(total(100, 50, discount=0.1))
print(total(100, 50, 0.1))150.0
135.0
150.1Look at the third line. 0.1 did not become the discount — it went into prices as one more price, and the answer is wrong with no error. To pass discount you have to write discount=0.1.
You can make a parameter keyword-only even when you do not want *args: put a bare * in the parameter list. Everything after it is keyword-only:
def send(message, *, retry=False):
return f"{message} (retry={retry})"
print(send("hi", retry=True))
print(send("hi", True))hi (retry=True)
TypeError: send() takes 1 positional argument but 2 were given(Before the second line Python also prints some traceback lines; only the last line is shown here.)
This is very useful for True/False settings: the caller has to write retry=True, so anyone reading the call can see what the True is for.
The order is fixed
Written together, the full rule is:
- Plain parameters first. Among them, the ones without a default come before the ones with a default.
- Then
*args(or a bare*). - Then keyword-only parameters — with or without defaults, in any order.
- Last,
**kwargs.
def f(a, b=1, *args, c, d=2, **kwargs):
print(a, b, args, c, d, kwargs)
f(10, c=3)
f(10, 20, 30, 40, c=3, e=5)10 1 () 3 2 {}
10 20 (30, 40) 3 2 {'e': 5}c has no default, yet it comes after b=1, which has one, and that is legal — because everything after *args arrives by name, so there is no doubt about where each value goes. The "defaults after" rule applies only to the part before *args.
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:
def order(item, quantity):
return f"{quantity} x {item}"
values = ["pen", 3]
fields = {"item": "bag", "quantity": 2}
print(order(*values))
print(order(**fields))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:
# 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"))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-77Four 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. And because discount comes after *items, it is keyword-only — it has to be passed as discount=0.1.
`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 no error appeared because the code inside the function works with either value. 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))`.
Step 4 of 6 — Predict
Check your understanding
quantity has a default. What do the two lines print?
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))- A1 x pen 5 x pen
- B1 x pen 1 x pen
- CA `TypeError`
- Dpen x 1 pen x 5
What kind of thing does *things gather?
def show(*things):
print(type(things))
print(len(things))
show(1, "two", 3.0)- A<class 'tuple'> 3
- B<class 'tuple'> 1
- C<class 'list'> 3
- D<class 'dict'> 3
The parameter with a default is placed first. What happens?
def order(quantity=1, item):
return item- AIt works normally
- BIt works, but calling `order("pen")` fails
- CA `SyntaxError` — a parameter with a default cannot precede a required one
- DA `TypeError`
Answering needs an account
Sign in to check your answers
The questions are above, and working them out in your head is the part that matters. Sign in to see the answers, the explanations and the three-level hints.
Your turn
Write a file called message.py containing one function:
def notify(recipient, *lines, subject="Notice", urgent=False, **details)It should return a readable message rather than print one, with the recipient, the subject, each line on its own row, and every pair from details as a note underneath. If urgent is True, add something before the subject.
Then call it four ways:
- With only a recipient and no lines at all
- With three lines and everything else left at its default
- With
urgent=Trueand asubject, both by name - By building a list and a dictionary and unpacking them with
*and**
Then break it three times on purpose, and see what happens each time:
- Move the
subject="Notice"parameter beforerecipient. Before the file even runs you getSyntaxError: parameter without a default follows parameter with a default. - Write
notify("rafi", subjekt="hi"), misspelled. There is no error:**detailsquietly accepts the wrong name too, sosubjektbecomes a note and the subject stays"Notice". Print the returned message and see it for yourself. - Write
notify("rafi", recipient="dia"). You getTypeError: notify() got multiple values for argument 'recipient'.
Python catches two of these mistakes at once and misses one — and that one is the most dangerous. This is the price of the convenience **kwargs gives: nobody checks the spelling any more. Recognise the messages once and you will catch them in seconds later, and you will also know where the silent mistake hides.
Step 6 of 6
Stretch — the chapter quiz
Ten questions from easy to hard. The last ones are difficult on purpose.
Sign in to take the quiz