Python Arena
Step 4 of 4

Functions that take functions

sorted, min and max get a function as an argument and call it themselves. Your own functions can do the same. A parameter can hold a function just as it holds a number or a list, and you call it with parentheses, like any function:

def apply_to_all(numbers, operation):
    results = []
    for number in numbers:
        results.append(operation(number))
    return results


def double(x):
    return 2 * x


print(apply_to_all([1, 2, 3], double))
print(apply_to_all([1, 2, 3], lambda x: x * x))
print(apply_to_all([-4, 5, -6], abs))
Output
[2, 4, 6]
[1, 4, 9]
[4, 5, 6]

apply_to_all doesn't know what operation does. It only expects a function that takes one number. Which function runs is up to the caller: a named one, a lambda or one of Python's own, such as abs, which gives a number without its minus sign. That makes one short function useful for many jobs.

The function you pass must accept the arguments it will be called with. Here operation gets two:

def combine(a, b, operation):
    return operation(a, b)


print(combine(12, 4, lambda x, y: x + y))
print(combine(12, 4, lambda x, y: x / y))
print(combine(12, 4, max))
Output
16
3.0
12

A common slip

Pass the function, not the result of calling it. Here double(5) runs first and gives 10, so operation is the number 10, and Python can't call a number:

def apply_to_all(numbers, operation):
    results = []
    for number in numbers:
        results.append(operation(number))
    return results


def double(x):
    return 2 * x


print(apply_to_all([1, 2, 3], double(5)))
Python stops with
Traceback (most recent call last):
  File "main.py", line 12, in <module>
    print(apply_to_all([1, 2, 3], double(5)))
          ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "main.py", line 4, in apply_to_all
    results.append(operation(number))
                   ~~~~~~~~~^^^^^^^^
TypeError: 'int' object is not callable

"Not callable" means the value isn't a function. Write double, without parentheses.

A function as a rule

A function argument is a neat way to pass a rule: which items to keep, to count or to look for. The function that gets the rule does the looping, and the caller only says what counts. So one count_matching can count long words, words with an o or negative numbers:

def count_matching(items, condition):
    count = 0
    for item in items:
        if condition(item):
            count += 1
    return count


def is_long(word):
    return len(word) > 4


words = ["sun", "moon", "star", "comet", "planet", "nebula"]
print(count_matching(words, is_long))
print(count_matching(words, lambda word: "o" in word))
print(count_matching([3, -1, 8], lambda n: n < 0))
Output
3
2
1

Your task

Challenge 1 of 3

The function keep returns the items for which the function wanted returns True. Fill in the blank so that the program prints:

[12, 20]
['ant', 'cat']

Challenge 2

What does this program print? Type each line it prints.

Challenge 3

Write a function first_match(items, condition) that returns the first item of the list for which the function condition returns True. If no item matches, it returns None. For example,

print(first_match([3, 8, 5, 12], lambda n: n > 6))
print(first_match(["ant", "bee"], lambda word: word.startswith("c")))

should print:

8
None