Kodewick
Lesson 17 of 31Contents

Function arguments

Lesson 17 of 31 · Part 3 · Functions · about 15 minutes

In lesson 16 every argument was required and matched up by position. Python gives you more choices than that. You have already met some of them: print("09", "45", sep=":") passes sep by name, and leaving it out gives a default of one space. This lesson shows how to build the same options into your own functions.

main.py
1def greet(name, greeting="Good evening"):2    print(f"{greeting}, {name}!")3 4greet("Ada")5greet("Ben", "Good morning")
Output
Good evening, Ada!
Good morning, Ben!

Default values

Write parameter=value in the def line, and that parameter gets a default value: the caller may leave it out. A parameter without a default must always be given.

main.py
1def refill(lamp, liters=2):2    return f"{lamp}: +{liters} L"3 4print(refill("Mill Road"))5print(refill("Stone Bridge", 5))
Output
Mill Road: +2 L
Stone Bridge: +5 L

Parameters with defaults come after the ones without. Otherwise Python could not tell which argument belongs where.

Keyword arguments

When you call a function, you can name the parameter: liters=5. That is a keyword argument. Named arguments can come in any order, and they make a call easier to read.

main.py
1def deliver(ore, count, to="warehouse"):2    print(f"{count} {ore} to the {to}")3 4deliver("iron", 12)5deliver(count=3, ore="gold")6deliver("coal", 8, to="smithy")
Output
12 iron to the warehouse
3 gold to the warehouse
8 coal to the smithy

You can mix both kinds, but the positional arguments come first. Once you name one argument, all the ones after it need names too.

Guess first

What does this print?

1def box(width, height=1, depth=1):2    return width * height * depth3 4print(box(2, depth=5))

The mutable default trap

A default value is made once, when Python runs the def line, not each time the function is called. For a number or a string that never matters. For a list it does: every call that leaves the argument out shares the very same list.

main.py
1def add_ore(item, cart=[]):2    cart.append(item)3    return cart4 5print(add_ore("iron"))6print(add_ore("gold"))7print(add_ore("coal"))
Output
['iron']
['iron', 'gold']
['iron', 'gold', 'coal']

Each call was meant to start with an empty cart, but the ore piles up. Press Step and watch cart: on the second call it already holds 'iron'.

The usual fix is a default of None, and a new list made inside the function. cart is None asks "is cart exactly None?", which is the standard way to check for None.

main.py
1def add_ore(item, cart=None):2    if cart is None:3        cart = []4    cart.append(item)5    return cart6 7print(add_ore("iron"))8print(add_ore("gold"))
Output
['iron']
['gold']

Any number of arguments: *args

max() and print() take as many arguments as you like. Your functions can too. Put * before a parameter name, and it collects all the extra positional arguments into a tuple. The usual name is args, but any name works.

main.py
1def total_ore(*loads):2    print("loads:", loads)3    return sum(loads)4 5print(total_ore(4, 8, 2))6print(total_ore(5))7print(total_ore())
Output
loads: (4, 8, 2)
14
loads: (5,)
5
loads: ()
0

Any keyword arguments: **kwargs

Two stars, **, collect all the extra keyword arguments into a dictionary: each name becomes a key. The usual name is kwargs.

main.py
1def stock_report(place, **counts):2    print(place)3    for item, count in counts.items():4        print(f"  {item}: {count}")5 6stock_report("Warehouse", iron=14, copper=3, gold=1)
Output
Warehouse
  iron: 14
  copper: 3
  gold: 1

The order of parameters

You can combine all of these in one def. They always go in this order:

OrderKindExample
1normal parametersname
2with a defaultgreeting="Hi"
3extra positional arguments*args
4after *args: keyword-onlysep=" "
5extra keyword arguments**kwargs

A parameter after *args can only be given by name, because every extra positional argument is swallowed by *args. That is exactly how print's sep and end work. Here is an example with the other four kinds; watch where each argument lands:

main.py
1def log(event, level=1, *details, **tags):2    print(event, level, details, tags)3 4log("delivery")5log("delivery", 2, "iron", 12, cart=3)
Output
delivery 1 () {}
delivery 2 ('iron', 12) {'cart': 3}

In a call, a positional argument after a keyword argument is a SyntaxError, so nothing in the program runs:

main.py
1def deliver(ore, count):2    print(count, ore)3 4deliver(ore="iron", 12)
Output
  File "main.py", line 4
    deliver(ore="iron", 12)
SyntaxError: positional argument follows keyword argument

Unpacking in a call

The stars also work the other way around, in a call. * spreads a list or tuple out into separate positional arguments; ** spreads a dictionary into keyword arguments.

main.py
1def deliver(ore, count, to="warehouse"):2    print(f"{count} {ore} to the {to}")3 4order = ["iron", 12]5deliver(*order)6details = {"ore": "gold", "count": 2, "to": "bank"}7deliver(**details)
Output
12 iron to the warehouse
2 gold to the bank

deliver(*order) is the same as deliver("iron", 12), and deliver(**details) is the same as writing out all three keyword arguments.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

A default price

Write price(count, each=4) that returns count * each.

It should print
12
60
exercise1.py
1# Write price() here2 3print(price(3))4print(price(3, each=20))
Output
Press Run or Ctrl+Enter
Exercise 2

Fresh carts

This function has the mutable default trap. Fix it so each call starts with an empty route.

It should print
['Mill Road']
['Market']
exercise2.py
1def plan(street, route=[]):2    route.append(street)3    return route4 5print(plan("Mill Road"))6print(plan("Market"))
Output
Press Run or Ctrl+Enter
Exercise 3

Longest name

Write longest(*names) that returns the longest of all the names it is given.

It should print
Cleo
Wick
exercise3.py
1# Write longest() here2 3print(longest("Ada", "Cleo", "Ben"))4print(longest("Wick"))
Output
Press Run or Ctrl+Enter

What you learned

  • def f(a, b=2): gives b a default value; parameters with defaults come after those without.
  • Keyword arguments, f(b=5, a=1), name the parameter and can come in any order, after the positional ones.
  • A default is made once. Never use a list or dictionary as a default; use None and make a new one inside.
  • *args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dictionary.
  • In a call, *list and **dict spread values out into separate arguments.