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.
- 1Encounter
- 2Understand
- 3Worked
- 4Predict
- 5Apply
- 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:
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. 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:
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.
The order is fixed
Written together, the order is: *plain, then `args, then ones with defaults, then kwargs`.
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:
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.
`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))`.
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
- Cpen x 1 pen x 5
- DA `TypeError`
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 'list'> 3
- C<class 'dict'> 3
- D<class 'tuple'> 1
The parameter with a default is placed first. What happens?
def order(quantity=1, item):
return item- AA `SyntaxError` — a parameter with a default cannot precede a required one
- BIt works, but calling `order("pen")` fails
- CIt works normally
- 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, reading each message:
- Move the parameter with a default above
recipient - Pass
subjekt="hi", misspelled - Write
notify("rafi", recipient="dia")
All three give a different SyntaxError or TypeError, and all three happen routinely in real work. Recognising the messages once means catching them in seconds later.
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