Learning Goals
3 minBy the end of this lesson you can:
- Give a parameter a default value, and call the function with or without it.
- Pass arguments by name, in any order, at the call.
- State the two ordering rules — defaults last in the
def, positional before keyword in the call. - Decide which arguments deserve names and which are obvious without them.
Warm-Up · What Does the Third One Mean?
5 minYou find this line in a classmate's program:
make_drink("Hot chocolate", 1, True, 0, False)- What is the
1? What is the0? - There are two
True/Falsevalues. Which is which? - How would you find out — and how long would it take?
You would have to scroll up and count the parameters in the def, in order, and hope you did not slip a place. By the end of this lesson that same call reads make_drink("Hot chocolate", sugar=1, ice=True) and needs no scrolling at all.
New Concept · Defaults and Names
12 min1 · A default value goes in the definition, with an =. If the caller says nothing, the default is used.
def order(item, qty=1): """Order qty of an item. One, if you don't say.""" print(f"{qty} x {item}") order("Pancake") # 1 x Pancake order("Pancake", 3) # 3 x Pancake
The parameter with a default has become optional. Nothing else changed: it is still an ordinary parameter, and you can still pass it by position.
def order(qty=1, item): is a SyntaxError, and it has to be. If the first parameter were optional, Python could not tell whether order("Tea") was giving a quantity or an item. Required first, optional after.
2 · Naming an argument at the call uses the same =, but on the other side:
def make_drink(name, size="regular", ice=True, sugar=1): """Print one drink order.""" ice_text = "with ice" if ice else "no ice" print(f"{size} {name}, {ice_text}, {sugar} sugar") make_drink("Hot chocolate") make_drink("Cocoa", size="large") make_drink("Orange juice", sugar=0, ice=False) make_drink("Strawberry milk", ice=False, size="large")
Output
regular Hot chocolate, with ice, 1 sugar large Cocoa, with ice, 1 sugar regular Orange juice, with ice, 0 sugar large Strawberry milk, no ice, 1 sugar
Look at the last two calls. sugar before ice in one, ice before size in the other — named arguments have no order, because the name says where the value goes. You also skip anything you do not care about, and its default fills in.
make_drink(size="large", "Cocoa") raises SyntaxError: positional argument follows keyword argument. Once you start naming, keep naming. In practice this is easy: the required things go first without names, the optional things follow with them.
When to name. Not every argument needs it. A good habit:
greet("Mia")— obvious. Leave it.set_alarm(7, 30)— probably hours and minutes. Borderline.report(marks, True, False)— name them. Two bare booleans are unreadable a week later.
Worked Example · One Receipt Printer, Many Shops
12 minDefaults let one function serve the common case in one word and the unusual case in two. Type this and run it.
def receipt(item, price, qty=1, discount=0, currency="$"): """Return one receipt line, with an optional per-item discount.""" gross = price * qty net = gross - gross * discount line = f"{qty} x {item:<14} {currency} {net:>6.2f}" if discount: line += f" ({int(discount * 100)}% off)" return line print(receipt("Pizza", 4.50)) print(receipt("Hot chocolate", 2.00, 3)) print(receipt("Pancake", 1.50, qty=10, discount=0.15)) print(receipt("Iced latte", 12.00, currency="SGD"))
Output
1 x Pizza $ 4.50 3 x Hot chocolate $ 6.00 10 x Pancake $ 12.75 (15% off) 1 x Iced latte SGD 12.00
Four calls, four different shapes, one function. Read them again and notice what you did not have to write: no 0 for "no discount", no "$" on every line, no 1 for a single item.
Choosing qty=1 and discount=0 is a statement about what is normal in your shop. Good defaults mean the common call is short and the unusual call is explicit — which is exactly the way round you want it. Defaults are documentation that runs.
One trap to know about now, before it bites you. A default is created once, when the def is read — not each time you call. That is invisible for numbers and strings, but it matters for lists:
def add_item(item, basket=[]): # DON'T do this
basket.append(item)
return basket
print(add_item("pancake")) # ['pancake']
print(add_item("tea")) # ['pancake', 'tea'] <- the same list!The safe pattern is basket=None, then build a fresh list inside:
def add_item(item, basket=None): """Add an item to a basket, starting a new one if none was given.""" if basket is None: basket = [] basket.append(item) return basket
Try It Yourself
13 minTake print_border(width) from FUN-02 and give it two defaults: width=34 and symbol="*". Then call it four ways — with nothing, with a width, with a symbol by name, and with both.
Hint
def print_border(width=34, symbol="*"): """Print a border line.""" print(symbol * width) print_border() print_border(20) print_border(symbol="=") print_border(20, symbol="-")
Here is a call with five bare arguments. Write the def it belongs to, give the last three sensible defaults, and rewrite the call so a stranger could read it.
send_report("2 Ceria", 6, True, False, "pdf")Hint
def send_report(class_name, term, include_marks=True, include_remarks=False, file_type="pdf"): ... send_report("2 Ceria", term=6, include_remarks=True)
Notice the second call is shorter as well as clearer, because two of the five values were already the defaults.
Mini-Challenge 🔥 · Debug: Three Calls, Two Errors
8 minTheo wrote a booking function. The def refuses to run at all, and once he fixes that, one of his three calls still fails. Find both problems, and name the error message each one produces.
def book_court(hour=18, name, players=2):
print(f"{name} booked court at {hour}:00 for {players}")
book_court("Mia")
book_court(name="Leo", hour=20)
book_court(players=4, "Nia")Answer
- The definition breaks rule 1.
hour=18has a default and sits beforename, which does not. Python stops withSyntaxError: parameter without a default follows parameter with a default. Fix:def book_court(name, hour=18, players=2):. - The third call breaks rule 2.
book_court(players=4, "Nia")puts a positional argument after a keyword one —SyntaxError: positional argument follows keyword argument. Fix:book_court("Nia", players=4).
The first two calls are fine once the def is corrected, and they show the pattern you want: the required thing first and unnamed, the optional things named.
Recap
3 minA default value in the def makes a parameter optional, and those parameters must come after the required ones. Naming arguments at the call frees you from the order and makes the call readable — but once you start naming, everything after it must be named too. Good defaults describe the normal case, so the common call is short. And never use a list as a default: use None and build a fresh one inside.
Vocabulary Card
- default value
- A value written into the
defwith=, used when the caller omits that argument. - keyword argument
- An argument passed by name at the call:
make_drink("Cocoa", size="large"). - positional argument
- An argument matched by its place. Must come before any keyword arguments.
- mutable default trap
- A list or dictionary default is created once and shared between calls. Use
Noneinstead.
Homework
4 minDesign send_message(to, text, ...) for a school messaging system. It must have at least three optional parameters with defaults you can justify — think about what is normal for a message to a parent.
Write six calls that between them use: no optional arguments, one, two out of order, all of them, and one call that would be much harder to read without names. Add a comment above each explaining what it does.
Hint: if you find yourself writing send_message("Puan Grace", "Late", True, True, False), that is the call the names were invented for.
Sample · messaging.py
# messaging.py - defaults chosen for the normal case: a plain, # non-urgent message in English, sent now, with no read receipt. def send_message(to, text, language="en", urgent=False, receipt=False, send_at="now"): """Return the message as one printable line.""" tag = "[URGENT] " if urgent else "" extras = " +receipt" if receipt else "" return f"{send_at:>5} -> {to:<12} {tag}{text} ({language}){extras}" # the ordinary case: two arguments and nothing else print(send_message("Puan Grace", "Mia arrived safely")) # one optional, by name print(send_message("Encik Reed", "Fees due Friday", language="zh")) # two optionals, deliberately out of order print(send_message("Puan Maya", "Please call the office", receipt=True, urgent=True)) # everything named print(send_message(to="Puan Grace", text="Sports day 8am", language="ms", urgent=False, receipt=True, send_at="07:00")) # the call that needs names: three booleans in a row is unreadable print(send_message("Encik Hart", "Detention today", urgent=True, receipt=True, send_at="15:30"))
Judge your defaults by the first call: if the ordinary message needs only to and text, they are right. If it still needs three extra arguments, the defaults are describing an unusual case rather than the normal one.