Know some Python? Write your own interactive tutorial, with runnable examples and exercises, and share it with a link. Write and share your own tutorial.

Start writing

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

The first call passes only one argument, so name is "Ada" and greeting falls back to its default, "Hello". The second call passes both, and "Good morning" replaces the default for that call only. The default is back for the next call.

A parameter with a default is optional; one without a default is required, and leaving it out is a TypeError. Parameters with defaults must come after the ones without, otherwise Python couldn't tell which argument fills which parameter. This is a SyntaxError:

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"))

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

The second call prints ['apple', 'pear']. Both calls used the same list object as their default, so the first call's append is still in it. Numbers, strings, True/False and None can't be changed in place, so they're always safe as defaults.

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

Now basket = [] runs inside the function, so each call that doesn't pass a basket gets a brand-new empty list.

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
Call How the values are matched
book_flight("London", "Paris", 2) by position: 1st, 2nd, 3rd
book_flight(destination="Rome", passengers=1, origin="Berlin") by name, in any order
book_flight("Madrid", passengers=3, destination="Lisbon") first by position, the rest by name

The names must match the parameter names exactly: book_flight(start="Oslo", ...) is a TypeError because there's no start parameter. Giving the same parameter twice, once by position and once by name, is also a TypeError.

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")

    order = f"A {size} coffee"
    if extras:
        order += " with " + " and ".join(extras)
    print(order)

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

make_coffee(sugar=2) skips straight to the third setting and leaves size and milk at their defaults. Without keyword arguments you'd have to write make_coffee("medium", False, 2), repeating the defaults just to reach sugar.

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

Inside the function, args is an ordinary tuple: (1, 2), then (5, 10, 15, 20), then () when nothing is passed, so sum(()) gives 0. It's the * that does the collecting; the name args is only a convention, and *numbers would work the same.

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

Inside the function, kwargs is a dictionary: {'name': 'Ada', 'job': 'Mathematician', 'born': 1815}. Each argument name becomes a key, written without quotes in the call.

In the def line Collects Arrives as
*args extra positional arguments a tuple
**kwargs extra keyword arguments a dictionary

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)
# TypeError: timeout or retries?
connect("example.com", 5)
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.

Share this tutorial

Know something worth teaching? Write your own tutorial with runnable examples and share it.