Learn Python / Function Arguments

Function Arguments

In the last lesson, every function took a fixed number of arguments in a fixed order. Python gives you much more flexibility: optional arguments with defaults, arguments passed by name, and functions that accept any number of values.

Default values

Give a parameter a default value with =. If the caller leaves that argument out, the default is used:

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Ada")
greet("Ada", "Good morning")
Output

Parameters with defaults must come after the ones without:

def greet(greeting="Hello", name):
    print(f"{greeting}, {name}!")
Output

You've already used defaults: print() has sep=" " and end="\n", and round() has ndigits=None.

A trap: mutable defaults

Never use a list or dictionary as a default value. The default is created once, when the function is defined, and then shared by every call:

def add_item(item, basket=[]):
    basket.append(item)
    return basket

print(add_item("apple"))
print(add_item("pear"))   # surprise: the apple is still there!
Output

Use None as the default and create a fresh list inside the function instead:

def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

print(add_item("apple"))
print(add_item("pear"))
Output

Keyword arguments

You can pass arguments by name. Then the order doesn't matter, and the call explains itself:

def book_flight(origin, destination, passengers):
    print(f"{passengers} passenger(s): {origin} -> {destination}")

book_flight("London", "Paris", 2)
book_flight(destination="Rome", passengers=1, origin="Berlin")
book_flight("Madrid", passengers=3, destination="Lisbon")
Output

Positional arguments must come before keyword arguments in a call:

def book_flight(origin, destination, passengers):
    print(origin, destination, passengers)

book_flight(origin="Oslo", "Paris", 2)
Output

Keyword arguments really shine with several optional settings. You set only the ones you need:

def make_coffee(size="medium", milk=False, sugar=0):
    extras = []
    if milk:
        extras.append("milk")
    if sugar:
        extras.append(f"{sugar} sugar")
    print(f"A {size} coffee" + (" with " + " and ".join(extras) if extras else ""))

make_coffee()
make_coffee(sugar=2)
make_coffee("large", milk=True)
Output

Any number of arguments: *args

Put a * before a parameter name to accept any number of positional arguments. They arrive as a tuple. By convention the parameter is called args:

def total(*args):
    print(args)
    return sum(args)

print(total(1, 2))
print(total(5, 10, 15, 20))
print(total())
Output

You can combine normal parameters with *args:

def introduce(greeting, *names):
    for name in names:
        print(f"{greeting}, {name}!")

introduce("Hi", "Ada", "Grace", "Alan")
Output

Any number of keyword arguments: **kwargs

Two stars collect any number of keyword arguments into a dictionary. By convention it's called kwargs:

def print_profile(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_profile(name="Ada", job="Mathematician", born=1815)
Output

Order of parameters

When you combine them, the order is: normal parameters, *args, keyword parameters with defaults, then **kwargs:

def report(title, *values, sep=", ", **options):
    print(title + ": " + sep.join(str(v) for v in values))
    print("options:", options)

report("Scores", 90, 85, 77, sep=" | ", color="blue", bold=True)
Output
Going deeper: Keyword-only arguments optional

A bare * in the parameter list means "everything after this must be passed by name". It stops callers from passing settings by position, where it's easy to forget which number means what:

def connect(host, *, timeout=10, retries=3):
    print(f"{host}: timeout={timeout}, retries={retries}")

connect("example.com", timeout=5)
connect("example.com", 5)   # TypeError: is 5 the timeout or the retries?
Output

You'll spot this in Python's own documentation. help(sorted) shows sorted(iterable, /, *, key=None, reverse=False): the * means key and reverse must be named, and the / means iterable must be passed by position, never as iterable=....

Unpacking when you call a function

The stars also work the other way round. * spreads a list or tuple into separate positional arguments, and ** spreads a dictionary into keyword arguments:

def describe(name, age, city):
    print(f"{name}, {age}, from {city}")

person = ["Ada", 36, "London"]
describe(*person)

details = {"name": "Grace", "age": 85, "city": "New York"}
describe(**details)
Output

You've seen this with print(), which takes any number of arguments:

numbers = [1, 2, 3, 4]
print(numbers)
print(*numbers)
print(*numbers, sep=" + ")
Output

Exercises

Exercise 1: Power with a default

Write power(base, exponent=2) that returns base raised to exponent. The calls below should print 25 and 125.

# define power here

print(power(5))
print(power(5, 3))
Output

Exercise 2: Average of any numbers

Write average(*numbers) that returns the average of however many numbers are passed, or 0 if none are.

# define average here

print(average(2, 4, 6))
print(average(10))
print(average())
Output

Exercise 3: Build a URL

Write build_url(domain, path="", **params) that returns a URL. Query parameters are joined as key=value with &, after a ?. The call below should print https://example.com/search?q=python&page=2.

# define build_url here

print(build_url("example.com", "search", q="python", page=2))
Output

Exercise 4: Fix the shared list

This function should start a fresh list each time, but the second call prints ['a', 'b']. Fix it so it prints ['a'] and then ['b'].

def collect(item, items=[]):
    items.append(item)
    return items

print(collect("a"))
print(collect("b"))
Output

Parameter kinds at a glance

A quick reference for later.

In the definition What it means Example call
def f(a, b) Required, passed by position or by name f(1, 2) or f(b=2, a=1)
def f(a, b=10) b is optional and defaults to 10 f(1) or f(1, 5)
def f(items=None) Safe default for a list or dict: create it inside the function f()
def f(*args) Any number of positional arguments, collected in a tuple f(1, 2, 3)
def f(**kwargs) Any number of keyword arguments, collected in a dictionary f(x=1, y=2)
def f(a, *, b) Everything after * must be passed by name f(1, b=2)
def f(a, /, b) Everything before / must be passed by position f(1, 2) or f(1, b=2)

And when calling a function:

In the call What it does Example
f(name=value) Passes an argument by name, in any order f(b=2, a=1)
f(*items) Unpacks a list or tuple into positional arguments f(*[1, 2]) is f(1, 2)
f(**options) Unpacks a dictionary into keyword arguments f(**{"b": 2}) is f(b=2)

Summary

  • param=value gives a parameter a default. Defaults go after required parameters.
  • Don't use a list or dictionary as a default. Use None and create one inside.
  • Keyword arguments (name=value) can be passed in any order and make calls clearer.
  • A starred parameter like *args collects extra positional arguments into a tuple.
  • A double-starred parameter like **kwargs collects extra keyword arguments into a dictionary.
  • In a call, *list and **dict unpack values into separate arguments.