Post

Functions in Python

def, parameters and return values, default and keyword arguments, the mutable default trap, docstrings and type hints, and passing a function as a value.

print(), len() and sorted() are functions someone else wrote. A function gives a name to a block of code: you write it once and run it as often as you need, with different values each time, so programs stay short and a bug gets fixed in one place.

Every example ran with Python 3.13, checked against the defining functions section of the tutorial and more on defining functions.

Defining and calling a function

The tutorial says: “The keyword def introduces a function definition. It must be followed by the function name and the parenthesized list of formal parameters.” The body is the indented block under it:

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

greet("Dana")
greet("Avi")
Hello, Dana!
Hello, Avi!

def only defines the function; the body runs when you call it with parentheses. name is a parameter: a variable that gets its value from the call. "Dana" is the argument passed in. A function can have no parameters, or several, separated by commas:

def show_line():
    print("-" * 20)

def describe(item, price):
    print(item, "costs", price)

show_line()
describe("tea", 12)
--------------------
tea costs 12

return: getting a value back

print() shows something on the screen, but the caller can’t use it. return hands a value back:

def area(width, height):
    return width * height

room = area(4, 3)
print(room, area(2, 2) + room)
12 16

return also ends the function right away. A function can have several return statements, for example one in each branch of an if:

def grade(score):
    if score >= 90:
        return "A"
    if score >= 70:
        return "B"
    return "C"

print(grade(95), grade(80), grade(40))
A B C

A function without return, or with a bare return, gives back None:

def shout(text):
    print(text.upper())

result = shout("hi")
print(result)
HI
None

To return several values, separate them with commas. Python packs them into a tuple, and the caller can unpack it:

def min_max(numbers):
    return min(numbers), max(numbers)

low, high = min_max([4, 9, 1, 7])
print(low, high)
1 9

Default values and keyword arguments

A parameter can have a default value. The caller may leave it out:

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

print(greet("Dana"))
print(greet("Dana", "Shalom"))
print(greet(greeting="Hi", name="Noa"))
Hello, Dana!
Shalom, Dana!
Hi, Noa!

The last call uses keyword arguments: name=value. With keywords, the order doesn’t matter, and the call explains itself. You’ve used them before: print(..., end=" ") and sorted(..., reverse=True). Parameters with defaults must come after the ones without.

Positional arguments go first, then keyword arguments. Passing the same parameter twice is an error:

greet("Dana", name="Avi")
TypeError: greet() got multiple values for argument 'name'

The mutable default trap

The tutorial has an “Important warning”: “The default value is evaluated only once.” When the default is a list or a dictionary, every call that doesn’t pass the argument shares the same object:

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

print(add_item("milk"))
print(add_item("bread"))
['milk']
['milk', 'bread']

The fix is to use None as the default and create the list inside the function:

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

print(add_item("milk"))
print(add_item("bread"))
['milk']
['bread']

Docstrings and type hints

A string on the first line of the body is the function’s docstring. Editors show it, and help() prints it. Type hints after the parameters and after -> say what types the function expects and returns:

def average(numbers: list[float]) -> float:
    """Return the mean of a non-empty list of numbers."""
    return sum(numbers) / len(numbers)

print(average([3, 4, 5]))
print(average.__doc__)
4.0
Return the mean of a non-empty list of numbers.

Hints are documentation. Python doesn’t check them when the code runs, as the TypeError post shows. Tools like mypy and your editor do.

Functions are values

A function is a value like any other. You can store it in a variable, put it in a list, or pass it to another function. That’s what key=len did in the lists post: it passed the function len to sorted(), without calling it:

def last_letter(word):
    return word[-1]

words = ["kiwi", "apple", "fig"]
print(sorted(words, key=last_letter))

shout = str.upper
print(shout("quiet"))
['apple', 'fig', 'kiwi']
QUIET

For a function this small, a lambda is a one-line version without a name: sorted(words, key=lambda w: w[-1]) gives the same result.

Common mistakes

  • Forgetting the parentheses. greet is the function itself. greet() calls it. print(greet) prints something like <function greet at 0x...>.
  • Using a function before it’s defined. The def line has to run before the call. Python reads the file top to bottom.

What’s next

Next in the series: code blocks and scope: why a variable created inside an if is still there after it, and which blocks do create a scope.